/*
sdk的初始化和组织
*/

/**
 * @fileoverview sensors analytic javascript sdk
 * @author shengyonggen@sensorsdata.cn
 * 神策数据 www.sensorsdata.cn ，长期招聘 前端SDK开发工程师 ，简历求发送到我邮箱，谢谢
 */

;(function(factory) {
  if (typeof exports === 'object' && typeof module === 'object') {
    module.exports = factory();
  } else{
    factory();
  }
})(function(){

try{



var sd = {};

var _ = sd._ = {};

// 压缩后的json库，照抄的github上的库
if(typeof JSON!=='object'){JSON={}}(function(){'use strict';var rx_one=/^[\],:{}\s]*$/,rx_two=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,rx_three=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,rx_four=/(?:^|:|,)(?:\s*\[)+/g,rx_escapable=/[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,rx_dangerous=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;function f(n){return n<10?'0'+n:n}function this_value(){return this.valueOf()}if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+f(this.getUTCMonth()+1)+'-'+f(this.getUTCDate())+'T'+f(this.getUTCHours())+':'+f(this.getUTCMinutes())+':'+f(this.getUTCSeconds())+'Z':null};Boolean.prototype.toJSON=this_value;Number.prototype.toJSON=this_value;String.prototype.toJSON=this_value}var gap,indent,meta,rep;function quote(string){rx_escapable.lastIndex=0;return rx_escapable.test(string)?'"'+string.replace(rx_escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key)}if(typeof rep==='function'){value=rep.call(holder,key,value)}switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null'}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null'}v=partial.length===0?'[]':gap?'[\n'+gap+partial.join(',\n'+gap)+'\n'+mind+']':'['+partial.join(',')+']';gap=mind;return v}if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){if(typeof rep[i]==='string'){k=rep[i];v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v)}}}}else{for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v)}}}}v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+mind+'}':'{'+partial.join(',')+'}';gap=mind;return v}}if(typeof JSON.stringify!=='function'){meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' '}}else if(typeof space==='string'){indent=space}rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify')}return str('',{'':value})}}if(typeof JSON.parse!=='function'){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}text=String(text);rx_dangerous.lastIndex=0;if(rx_dangerous.test(text)){text=text.replace(rx_dangerous,function(a){return'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})}if(rx_one.test(text.replace(rx_two,'@').replace(rx_three,']').replace(rx_four,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j}throw new SyntaxError('JSON.parse')}}}());


/*
一系列的通用的方法
*/

(function() {

  var ArrayProto = Array.prototype;
  var FuncProto = Function.prototype;
  var ObjProto = Object.prototype;
  var slice = ArrayProto.slice;
  var toString = ObjProto.toString;
  var hasOwnProperty = ObjProto.hasOwnProperty;
  var nativeBind = FuncProto.bind;
  var nativeForEach = ArrayProto.forEach;
  var nativeIndexOf = ArrayProto.indexOf;
  var nativeIsArray = Array.isArray;
  var breaker = {};

  var each = _.each = function(obj, iterator, context) {
    if (obj == null) {
      return false;
    }
    if (nativeForEach && obj.forEach === nativeForEach) {
      obj.forEach(iterator, context);
    } else if (obj.length === +obj.length) {
      for (var i = 0, l = obj.length; i < l; i++) {
        if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) {
          return false;
        }
      }
    } else {
      for (var key in obj) {
        if (hasOwnProperty.call(obj, key)) {
          if (iterator.call(context, obj[key], key, obj) === breaker) {
            return false;
          }
        }
      }
    }
  };

  // 普通的extend，不能到二级
  _.extend = function(obj) {
    each(slice.call(arguments, 1), function(source) {
      for (var prop in source) {
        if (source[prop] !== void 0) {
          obj[prop] = source[prop];
        }
      }
    });
    return obj;
  };
  // 允许二级的extend
  _.extend2Lev = function(obj) {
    each(slice.call(arguments, 1), function(source) {
      for (var prop in source) {
        if (source[prop] !== void 0) {
          if (_.isObject(source[prop]) && _.isObject(obj[prop])) {
            _.extend(obj[prop], source[prop]);
          } else {
            obj[prop] = source[prop];
          }
        }
      }
    });
    return obj;
  };
  // 如果已经有的属性不覆盖,如果没有的属性加进来
  _.coverExtend = function(obj) {
    each(slice.call(arguments, 1), function(source) {
      for (var prop in source) {
        if (source[prop] !== void 0 && obj[prop] === void 0) {
          obj[prop] = source[prop];
        }
      }
    });
    return obj;
  };

  _.isArray = nativeIsArray || function(obj) {
      return toString.call(obj) === '[object Array]';
    };

  _.isFunction = function(f) {
    if(!f){
      return false;
    }
    try {
      return /^\s*\bfunction\b/.test(f);
    } catch (x) {
      return false;
    }
  };

  _.isArguments = function(obj) {
    return !!(obj && hasOwnProperty.call(obj, 'callee'));
  };

  _.toArray = function(iterable) {
    if (!iterable) {
      return [];
    }
    if (iterable.toArray) {
      return iterable.toArray();
    }
    if (_.isArray(iterable)) {
      return slice.call(iterable);
    }
    if (_.isArguments(iterable)) {
      return slice.call(iterable);
    }
    return _.values(iterable);
  };

  _.values = function(obj) {
    var results = [];
    if (obj == null) {
      return results;
    }
    each(obj, function(value) {
      results[results.length] = value;
    });
    return results;
  };


  _.indexOf = function (arr, target) {
    var indexof = arr.indexOf;
    if (indexof) {
      return indexof.call(arr, target);
    }
    else {
      for (var i = 0; i < arr.length; i++) {
        if (target === arr[i]) {
          return i;
        }
      }
      return -1;
    }
  };

_.hasAttribute = function(ele, attr) {
  if (ele.hasAttribute) {
    return ele.hasAttribute(attr);
  } else {
    return !!(ele.attributes[attr] && ele.attributes[attr].specified);
  }
};

_.filter = function (arr, fn, self) {
  var hasOwn = Object.prototype.hasOwnProperty;
  if (arr.filter) {
    return arr.filter(fn);
  }
  var ret = [];
  for (var i = 0; i < arr.length; i++) {
    if (!hasOwn.call(arr, i)) {
      continue;
    }
    var val = arr[i];
    if (fn.call(self, val, i, arr)) {
      ret.push(val);
    }
  }
  return ret;
};

_.inherit = function(subclass, superclass) {
  subclass.prototype = new superclass();
  subclass.prototype.constructor = subclass;
  subclass.superclass = superclass.prototype;
  return subclass;
};

_.trim = function(str){
  return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
};

_.isObject = function(obj) {
  if(obj == null){
    return false;
  }else{
    return (toString.call(obj) == '[object Object]');
  }
};

_.isEmptyObject = function(obj) {
  if (_.isObject(obj)) {
    for (var key in obj) {
      if (hasOwnProperty.call(obj, key)) {
        return false;
      }
    }
    return true;
  }
  return false;
};

_.isUndefined = function(obj) {
  return obj === void 0;
};

_.isString = function(obj) {
  return toString.call(obj) == '[object String]';
};

_.isDate = function(obj) {
  return toString.call(obj) == '[object Date]';
};

_.isBoolean = function(obj) {
  return toString.call(obj) == '[object Boolean]';
};

_.isNumber = function(obj) {
  return (toString.call(obj) == '[object Number]' && /[\d\.]+/.test(String(obj)));
};

_.isElement = function(obj) {
    return !!(obj && obj.nodeType === 1);
};

_.isJSONString = function(str) {
  try {
    JSON.parse(str);
  } catch (e) {
    return false;
  }
  return true;
};
_.safeJSONParse = function(str){
  var val = null;
  try {
    val = JSON.parse(str);
  } catch (e) {
    return false;
  }
  return val;
};
// gbk等编码decode会异常
_.decodeURIComponent = function(val){
  var result = val;
  try{
    result = decodeURIComponent(val);
  }catch(e){
    result = val;
  }
  return result;
};

_.encodeDates = function(obj) {
  _.each(obj, function(v, k) {
    if (_.isDate(v)) {
      obj[k] = _.formatDate(v);
    } else if (_.isObject(v)) {
      obj[k] = _.encodeDates(v); // recurse
    }
  });
  return obj;
};

_.mediaQueriesSupported = function() {
  return (typeof window.matchMedia != "undefined" || typeof window.msMatchMedia != "undefined");
};

// Returns current screen orientation type
// Possible values: ['未取到值', 'landscape', 'portrait']
// Defaults to '未取到值'.
// Tested on
// * IE 6 => '未取到值'
// * Opera 15 on macOS
// * Firefox 68 on macOS
// * Safari 12.1 on macOS
// * Chrome 75 on macOS
// * Safari on iPhone X
// * Chrome on Google Pixel 2
_.getScreenOrientation = function() {
  // Screen Orientation API
  var screenOrientationAPI = screen.msOrientation || screen.mozOrientation || (screen.orientation || {}).type;
  var screenOrientation = '未取到值';
  if (screenOrientationAPI) {
    screenOrientation = screenOrientationAPI.indexOf('landscape') > -1 ? 'landscape' : 'portrait';
  } else if (_.mediaQueriesSupported()) { // matchMedia
    var matchMediaFunc = window.matchMedia || window.msMatchMedia;
    if (matchMediaFunc("(orientation: landscape)").matches) {
      screenOrientation = 'landscape';
    } else if (matchMediaFunc("(orientation: portrait)").matches) {
      screenOrientation = 'portrait';
    }
  }
  return screenOrientation;
};

_.now = Date.now || function() {
  return new Date().getTime();
};

_.throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;
    var previous = 0;
    if (!options) options = {};
    var later = function() {
      previous = options.leading === false ? 0 : _.now();
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };
    return function() {
      var now = _.now();
      if (!previous && options.leading === false) previous = now;
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0 || remaining > wait) {
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
};

_.hashCode = function(str){
  if(typeof str !== 'string'){
    return 0;
  }
  var hash = 0;
  var char = null;
  if (str.length == 0) {
    return hash;
  }
  for (var i = 0; i < str.length; i++) {
    char = str.charCodeAt(i);
    hash = ((hash<<5)-hash)+char;
    hash = hash & hash;
  }
  return hash;
};

_.formatDate = function(d) {
  function pad(n) {
    return n < 10 ? '0' + n : n;
  }

  return d.getFullYear() + '-'
    + pad(d.getMonth() + 1) + '-'
    + pad(d.getDate()) + ' '
    + pad(d.getHours()) + ':'
    + pad(d.getMinutes()) + ':'
    + pad(d.getSeconds()) + '.'
    + pad(d.getMilliseconds());
};

// 把日期格式全部转化成日期字符串
_.searchObjDate = function(o) {
  if (_.isObject(o)) {
    _.each(o, function(a, b) {
      if (_.isObject(a)) {
        _.searchObjDate(o[b]);
      } else {
        if (_.isDate(a)) {
          o[b] = _.formatDate(a);
        }
      }
    });
  }
};

_.searchZZAppStyle = function(data){
  if(typeof data.properties.$project !== 'undefined'){
    data.project = data.properties.$project;
    delete data.properties.$project;
  }
  if(typeof data.properties.$token !== 'undefined'){
    data.token = data.properties.$token;
    delete data.properties.$token;
  }
};

_.formatJsonString = function(obj){
  try{
    return JSON.stringify(obj, null, '  ');
  }catch(e){
    return JSON.stringify(obj);
  }
};

// 把字符串格式数据限制字符串长度
_.formatString = function(str) {
  if (str.length > sd.para.max_string_length) {
    sd.log('字符串长度超过限制，已经做截取--' + str);
    return str.slice(0, sd.para.max_string_length);
  } else {
    return str;
  }
};

// 把字符串格式数据限制字符串长度
_.searchObjString = function(o) {
  if (_.isObject(o)) {
    _.each(o, function(a, b) {
      if (_.isObject(a)) {
        _.searchObjString(o[b]);
      } else {
        if (_.isString(a)) {
          o[b] = _.formatString(a);
        }
      }
    });
  }
};

/**
 * 执行属性中的函数，并且过滤掉不符合条件的属性
 *
 * @param {Object} obj - Properties.
 *
 */
_.parseSuperProperties = function(obj) {
  if (_.isObject(obj)) {
    _.each(obj, function(value, key) {
      if (_.isFunction(value)) {
        try {
          obj[key] = value();
          if (_.isFunction(obj[key])) {
            sd.log("您的属性- " + key + ' 格式不满足要求，我们已经将其删除');
            delete obj[key];
          }
        } catch (e) {
          delete obj[key];
          sd.log("您的属性- " + key + ' 抛出了异常，我们已经将其删除');
        }
      }
    });
    _.strip_sa_properties(obj);
  }
};


/**
 * @description 过滤掉事件的属性名为保留字段的属性
 * @param {object} obj 属性对象
 */
_.filterReservedProperties = function(obj){
  var reservedFields =['distinct_id','user_id','id','date','datetime','event','events','first_id','original_id','device_id','properties','second_id','time','users'];
  if(!_.isObject(obj)){
    return;
  }
  _.each(reservedFields, function(key,index){
    if(!(key in obj)){
      return;
    }
    if(index < 3){
      delete obj[key];
      sd.log("您的属性- " + key + '是保留字段，我们已经将其删除')
    }else{
      sd.log("您的属性- " + key + '是保留字段，请避免其作为属性名')
    }
  });
}

// 去除$option的配置数据
_.searchConfigData = function(data){
  if(typeof data === 'object' && data.$option){
    var data_config = data.$option;
    delete data.$option;
    return data_config;
  }else{
    return {};
  }
}


// 数组去重复
_.unique = function(ar) {
  var temp, n = [], o = {};
  for (var i = 0; i < ar.length; i++) {
    temp = ar[i];
    if (!(temp in o)) {
      o[temp] = true;
      n.push(temp);
    }
  }
  return n;
};

// 只能是sensors满足的数据格式
_.strip_sa_properties = function(p) {
  if (!_.isObject(p)) {
    return p;
  }
  _.each(p, function(v, k) {
    // 如果是数组，把值自动转换成string
    if (_.isArray(v)) {
      var temp = [];
      _.each(v, function(arrv) {
        if (_.isString(arrv)) {
          temp.push(arrv);
        } else {
          sd.log('您的数据-',k, v, '的数组里的值必须是字符串,已经将其删除');
        }
      });
      if (temp.length !== 0) {
        p[k] = temp;
      } else {
        delete p[k];
        sd.log('已经删除空的数组');
      }
    }
    // 只能是字符串，数字，日期,布尔，数组
    if (!(_.isString(v) || _.isNumber(v) || _.isDate(v) || _.isBoolean(v) || _.isArray(v) || _.isFunction(v) || (k === '$option'))) {
      sd.log('您的数据-',k, v, '-格式不满足要求，我们已经将其删除');
      delete p[k];
    }
  });
  return p;
};

// 去掉undefined和null
_.strip_empty_properties = function(p) {
  var ret = {};
  _.each(p, function(v, k) {
    if (v != null) {
      ret[k] = v;
    }
  });
  return ret;
};

_.utf8Encode = function(string) {
  string = (string + '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');

  var utftext = '', start, end;
  var stringl = 0, n;

  start = end = 0;
  stringl = string.length;

  for (n = 0; n < stringl; n++) {
    var c1 = string.charCodeAt(n);
    var enc = null;

    if (c1 < 128) {
      end++;
    } else if ((c1 > 127) && (c1 < 2048)) {
      enc = String.fromCharCode((c1 >> 6) | 192, (c1 & 63) | 128);
    } else {
      enc = String.fromCharCode((c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128);
    }
    if (enc !== null) {
      if (end > start) {
        utftext += string.substring(start, end);
      }
      utftext += enc;
      start = end = n + 1;
    }
  }

  if (end > start) {
    utftext += string.substring(start, string.length);
  }

  return utftext;
};

/**
 * Create a base-64 encoded ASCII string from a UTF-8 string.
 */
_.base64Encode = function(data) {
  if (typeof btoa === 'function') {
    return btoa(encodeURIComponent(data).replace(/%([0-9A-F]{2})/g, function (match, p1) {
      return String.fromCharCode('0x' + p1);
    }));
  }
  data = String(data);
  var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc = '', tmp_arr = [];
  if (!data) {
    return data;
  }
  data = _.utf8Encode(data);
  do {
    o1 = data.charCodeAt(i++);
    o2 = data.charCodeAt(i++);
    o3 = data.charCodeAt(i++);

    bits = o1 << 16 | o2 << 8 | o3;

    h1 = bits >> 18 & 0x3f;
    h2 = bits >> 12 & 0x3f;
    h3 = bits >> 6 & 0x3f;
    h4 = bits & 0x3f;
    tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
  } while (i < data.length);

  enc = tmp_arr.join('');

  switch (data.length % 3) {
    case 1:
      enc = enc.slice(0, -2) + '==';
      break;
    case 2:
      enc = enc.slice(0, -1) + '=';
      break;
  }

  return enc;
};


_.UUID = (function() {
  var T = function() {
    var d = 1 * new Date(), i = 0;
    while (d == 1 * new Date()) {
      i++;
    }
    return d.toString(16) + i.toString(16);
  };
  var R = function() {
    return Math.random().toString(16).replace('.', '');
  };
  var UA = function(n) {
    var ua = navigator.userAgent, i, ch, buffer = [], ret = 0;

    function xor(result, byte_array) {
      var j, tmp = 0;
      for (j = 0; j < byte_array.length; j++) {
        tmp |= (buffer[j] << j * 8);
      }
      return result ^ tmp;
    }

    for (i = 0; i < ua.length; i++) {
      ch = ua.charCodeAt(i);
      buffer.unshift(ch & 0xFF);
      if (buffer.length >= 4) {
        ret = xor(ret, buffer);
        buffer = [];
      }
    }

    if (buffer.length > 0) {
      ret = xor(ret, buffer);
    }

    return ret.toString(16);
  };

  return function() {
    // 有些浏览器取个屏幕宽度都异常...
    var se = String(screen.height * screen.width);
    if (se && /\d{5,}/.test(se)) {
      se = se.toString(16);
    } else {
      se = String(Math.random() * 31242).replace('.', '').slice(0, 8);
    }
    var val = (T() + '-' + R() + '-' + UA() + '-' + se + '-' + T());
    if(val){
      return val;
    }else{
      return (String(Math.random()) + String(Math.random()) + String(Math.random())).slice(2, 15);
    }

  };
})();


_.getQueryParam = function(url, param) {
  param = param.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
  url = _.decodeURIComponent(url);
  var regexS = "[\\?&]" + param + "=([^&#]*)",
    regex = new RegExp(regexS),
    results = regex.exec(url);
  if (results === null || (results && typeof(results[1]) !== 'string' && results[1].length)) {
    return '';
  } else {
    return _.decodeURIComponent(results[1]);
  }
};

_.urlParse = function(para) {
  var URLParser = function(a) {
    this._fields = {
      Username: 4,
      Password: 5,
      Port: 7,
      Protocol: 2,
      Host: 6,
      Path: 8,
      URL: 0,
      QueryString: 9,
      Fragment: 10
    };
    this._values = {};
    this._regex = null;
    this._regex = /^((\w+):\/\/)?((\w+):?(\w+)?@)?([^\/\?:]+):?(\d+)?(\/?[^\?#]+)?\??([^#]+)?#?(\w*)/;

    if (typeof a != 'undefined') {
      this._parse(a)
    }
  };
  URLParser.prototype.setUrl = function(a) {
    this._parse(a)
  };
  URLParser.prototype._initValues = function() {
    for (var a in this._fields) {
      this._values[a] = ''
    }
  };
  URLParser.prototype.addQueryString = function(queryObj) {
      if(typeof queryObj !== 'object'){
          return false;
      }
      var query = this._values.QueryString || '';
      for(var i in queryObj){
         if(new RegExp(i+'[^&]+').test(query)){
            query = query.replace(new RegExp(i+'[^&]+'), i + '=' + queryObj[i]);
         }else{
             if(query.slice(-1) === '&'){
                 query = query + i + '=' + queryObj[i];
             }else{
              if(query === ''){
                query = i + '=' + queryObj[i];
              }else{
                query = query + '&' + i + '=' + queryObj[i];
              }
             }
         }
      }
      this._values.QueryString = query;
  };
  URLParser.prototype.getUrl = function() {
    var url = '';
    url += this._values.Origin;
    url += this._values.Port ? ':' + this._values.Port : '';
    url += this._values.Path;
    url += this._values.QueryString ? '?' + this._values.QueryString : '';
    url += this._values.Fragment ? '#'+this._values.Fragment : '';
    return url;
  };

  URLParser.prototype.getUrl = function() {
    var url = '';
    url += this._values.Origin;
    url += this._values.Port ? ':' + this._values.Port : '';
    url += this._values.Path;
    url += this._values.QueryString ? '?' + this._values.QueryString : '';
    return url;
  };
  URLParser.prototype._parse = function(a) {
    this._initValues();
    var b = this._regex.exec(a);
    if (!b) {
      throw 'DPURLParser::_parse -> Invalid URL'
    }
    for (var c in this._fields) {
      if (typeof b[this._fields[c]] != 'undefined') {
        this._values[c] = b[this._fields[c]]
      }
    }
    this._values['Hostname'] = this._values['Host'].replace(/:\d+$/, '');
    this._values['Origin'] = this._values['Protocol'] + '://' + this._values['Hostname'];

  };
  return new URLParser(para);
};

/* ulr预置
_.referringDomain = function(referrer) {
  var split = referrer.split('/');
  if (split.length >= 3) {
    return split[2];
  }
  return '';
};

_.getDomainByHost = function(url) {
  if (typeof url === 'string' && url.split('.').length >= 2) {
    var temp = url.match(/[^\.]+\.[^.]+$/);
    if (temp && temp[0]) {
      return temp[0];
    } else {
      return '';
    }
  } else {
    return '';
  }
}
*/

/* _.draggable = function(elementToDrag, event) {
  function getScrollOffsets() {
    var w = document;
    if (w.pageXOffset != null) {
      return {
        x: w.pageXOffset,
        y: w.pageYOffset
      };
    } else {
      return {
        x: w.documentElement.scrollLeft,
        y: w.documentElement.scrollTop
      };
    }
  }

  var scroll = getScrollOffsets();
  var startX = event.clientX + scroll.x;
  var startY = event.clientY + scroll.y;
  var origX = elementToDrag.offsetLeft;
  var origY = elementToDrag.offsetTop;
  var deltaX = startX - origX;
  var deltaY = startY - origY;
  if (document.addEventListener) {
    document.addEventListener("mousemove", moveHandler);
    document.addEventListener("mouseup", upHandler);
  } else if (document.attachEvent) {
    document.attachEvent("onmousemove", moveHandler);
    document.attachEvent("onmouseup", upHandler);
  }
  if (event.stopPropagation) event.stopPropagation();
  else event.cancelBubble = true;
  if (event.preventDefault) event.preventDefault();
  else event.returnValue = false;

  elementToDrag.style.bottom = 'auto';

  function moveHandler(e) {
    e = e || window.event;
    var scroll = getScrollOffsets();
    elementToDrag.style.left = (e.clientX + scroll.x - deltaX) + "px";
    elementToDrag.style.top = (e.clientY + scroll.y - deltaY) + "px";
    if (e.stopPropagation) e.stopPropagation();
    else e.cancelBubble = true;
  }
  function upHandler(e) {
    if (!e) e = window.event;
    if (document.removeEventListener) {
      document.removeEventListener("mouseup", upHandler);
      document.removeEventListener("mousemove", moveHandler);
    } else if (document.detachEvent) {
      document.detachEvent("onmouseup", upHandler);
      document.detachEvent("onmousemove", moveHandler);
    }
    if (e.stopPropagation) e.stopPropagation();
    else e.cancelBubble = true;
  }
} */

// 是否有标准的浏览器环境,如果不是发送$errorEnviroment:{$errorReson:'没有window'}
/* _.hasStandardBrowserEnviroment = function() {
  if (!window) {
    return 'window';
  }
  if (!document) {
    return 'document';
  }
  if (!navigator) {
    return 'navigator';
  }
  if (!screen) {
    return 'screen';
  }

}; */

/* _.bindReady = function(fn,win) {
  win = win || window;
  var done = false,
  top = true,
  doc = win.document,
  root = doc.documentElement,
  modern = doc.addEventListener,
  add = modern ? 'addEventListener' : 'attachEvent',
  rem = modern ? 'removeEventListener' : 'detachEvent',
  pre = modern ? '' : 'on',
  init = function(e) {
    if (e.type == 'readystatechange' && doc.readyState != 'complete') return;
    (e.type == 'load' ? win : doc)[rem](pre + e.type, init, false);
    if (!done && (done = true)) fn.call(win, e.type || e);
  },
  poll = function() {
    try { root.doScroll('left'); } catch(e) { setTimeout(poll, 50); return; }
    init('poll');
  };

  if (doc.readyState == 'complete') fn.call(win, 'lazy');
  else {
    if (!modern && root.doScroll) {
      try { top = !win.frameElement; } catch(e) { }
      if (top) poll();
    }
    doc[add](pre + 'DOMContentLoaded', init, false);
    doc[add](pre + 'readystatechange', init, false);
    win[add](pre + 'load', init, false);
  }

}; */


_.addEvent = function() {

    function fixEvent(event) {
        if (event) {
            event.preventDefault = fixEvent.preventDefault;
            event.stopPropagation = fixEvent.stopPropagation;
            event._getPath = fixEvent._getPath;
        }
        return event;
    }
    fixEvent._getPath = function(){
      var ev = this;
      var polyfill = function () {
        try{
          var element = ev.target;
          var pathArr = [element];
          if (element === null || element.parentElement === null) {
              return [];
          }
          while (element.parentElement !== null) {
              element = element.parentElement;
              pathArr.unshift(element);
          }
          return pathArr;
        }catch(err){
          return [];
        }

      };
      return this.path || (this.composedPath && this.composedPath()) || polyfill();
    };
    fixEvent.preventDefault = function() {
        this.returnValue = false;
    };
    fixEvent.stopPropagation = function() {
        this.cancelBubble = true;
    };


    var register_event = function(element, type, handler) {
        var useCapture = _.isObject(sd.para.heatmap) && sd.para.heatmap.useCapture ? true : false;
        if (element && element.addEventListener) {
            element.addEventListener(type, function(e){
              e._getPath = fixEvent._getPath;
              handler.call(this,e);
            }, useCapture);
        } else {
            var ontype = 'on' + type;
            var old_handler = element[ontype];
            element[ontype] = makeHandler(element, handler, old_handler);
        }
    };
    function makeHandler(element, new_handler, old_handlers) {
        var handler = function(event) {
            event = event || fixEvent(window.event);
            if (!event) {
                return undefined;
            }
            event.target = event.srcElement;

            var ret = true;
            var old_result, new_result;
            if (typeof old_handlers === 'function') {
                old_result = old_handlers(event);
            }
            new_result = new_handler.call(element, event);
            if ((false === old_result) || (false === new_result)) {
                ret = false;
            }
            return ret;
        };
        return handler;
    }

    register_event.apply(null,arguments);
};


_.addHashEvent = function(callback){
  var hashEvent = ('pushState' in window.history ? 'popstate' : 'hashchange');
  _.addEvent(window,hashEvent,callback);
};

_.addSinglePageEvent = function(callback){
  var current_url = location.href;
  var historyPushState = window.history.pushState;
  var historyReplaceState = window.history.replaceState;

  //调用方法导致的url切换
  window.history.pushState = function () {
    historyPushState.apply(window.history, arguments);
    callback(current_url);
    current_url = location.href;
  };
  window.history.replaceState = function () {
    historyReplaceState.apply(window.history,arguments);
    callback(current_url);
    current_url = location.href;
  };

  // 前进后退导致的url切换
    var singlePageEvent = historyPushState ? 'popstate' : 'hashchange';
    _.addEvent(window,singlePageEvent,function(){
      callback(current_url);
      current_url = location.href;
    });
};

_.cookie = {
  get: function(name) {
    var nameEQ = name + '=';
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++) {
      var c = ca[i];
      while (c.charAt(0) == ' ') {
        c = c.substring(1, c.length);
      }
      if (c.indexOf(nameEQ) == 0) {
        return _.decodeURIComponent(c.substring(nameEQ.length, c.length));
      }
    }
    return null;
  },
  set: function(name, value, days, cross_subdomain, is_secure) {
    cross_subdomain = typeof cross_subdomain === 'undefined' ? sd.para.cross_subdomain : cross_subdomain;
    var cdomain = '', expires = '', secure = '';
    days = days == null ? 73000 : days;

    if (cross_subdomain) {
      var domain = _.getCurrentDomain(location.href);
      if(domain === 'url解析失败'){
        domain = '';
      }
      cdomain = ((domain) ? '; domain=' + domain : '');
    }

    // 0 session
    // -1 马上过期
    //
    if (days !== 0) {
      var date = new Date();
      // 默认是天，可以是秒
      if (String(days).slice(-1) === 's') {
        date.setTime(date.getTime() + (Number(String(days).slice(0, -1)) * 1000));
      } else {
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
      }

      expires = '; expires=' + date.toGMTString();
    }

    if (is_secure) {
      secure = '; secure';
    }

    document.cookie = name + '=' + encodeURIComponent(value) + expires
      + '; path=/' + cdomain + secure;
  },

  remove: function(name, cross_subdomain) {
    cross_subdomain = typeof cross_subdomain === 'undefined' ? sd.para.cross_subdomain : cross_subdomain;
    _.cookie.set(name, '', -1, cross_subdomain);

  },

  getCookieName: function(name_prefix, url){
    var sub = '';
    url = url || location.href;
    if(sd.para.cross_subdomain === false){
      try {
        sub = _.URL(url).hostname;
      } catch (e) {
        // do nothing
      }
      if(typeof sub === 'string' && sub !== ''){
        sub = 'sajssdk_2015_' + name_prefix + '_' + sub.replace(/\./g, '_');
      }else{
        sub = 'sajssdk_2015_root_' + name_prefix;
      }
    }else{
      sub = 'sajssdk_2015_cross_' + name_prefix;
    }
    return sub;
  },
// 针对新用户的兼容性判断,兼容子域名
  getNewUser: function(){
    var prefix = 'new_user';
    if(this.get('sensorsdata_is_new_user') !== null || this.get(this.getCookieName(prefix)) !== null){
      return true;
    }else{
      return false;
    }
  }
};

// 获取元素的一些信息
_.getEleInfo = function(obj){
  if(!obj.target){
    return false;
  }

  var target = obj.target;
  var tagName = target.tagName.toLowerCase();


  var props = {};

  props.$element_type = tagName;
  props.$element_name = target.getAttribute('name');
  props.$element_id = target.getAttribute('id');
  props.$element_class_name = typeof target.className === 'string' ? target.className : null;
  props.$element_target_url = target.getAttribute('href');

  // 获取内容

  var textContent = '';
  if (target.textContent) {
    textContent = _.trim(target.textContent);
  }else if(target.innerText){
    textContent = _.trim(target.innerText);
  }
  if (textContent) {
    textContent = textContent.replace(/[\r\n]/g, ' ').replace(/[ ]+/g, ' ').substring(0, 255);
  }
  props.$element_content = textContent || '';

  // 针对inut默认只采集button和submit非名感的词汇。可以自定义（银联提）
  if(tagName === 'input'){
    if(target.type === 'button' || target.type === 'submit'){
      props.$element_content = target.value || '';
    } else if (sd.para.heatmap && (typeof sd.para.heatmap.collect_input === 'function') && sd.para.heatmap.collect_input(target)){
      props.$element_content = target.value || '';
    }
  }

  props = _.strip_empty_properties(props);

  props.$url = location.href;
  props.$url_path = location.pathname;
  props.$title = document.title;
  props.$viewport_width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 0;

  return props;

};

// _.localStorage
_.localStorage = {
  get: function(name) {
    return window.localStorage.getItem(name);
  },

  parse: function(name) {
    var storedValue;
    try {
      storedValue = JSON.parse(_.localStorage.get(name)) || null;
    } catch (err) {
    }
    return storedValue;
  },

  set: function(name, value) {
    window.localStorage.setItem(name, value);
  },

  remove: function(name) {
    window.localStorage.removeItem(name);
  },

  isSupport: function() {
    var supported = true;
    try {
      var key = '__sensorsdatasupport__';
      var val = 'testIsSupportStorage';
      _.localStorage.set(key, val);
      if (_.localStorage.get(key) !== val) {
        supported = false;
      }
      _.localStorage.remove(key);
    } catch (err) {
      supported = false;
    }
    return supported;
  }

};

_.sessionStorage = {

  isSupport:function(){
      var supported = true;

      var key = '__sensorsdatasupport__';
      var val = 'testIsSupportStorage';
      try{
        if(sessionStorage && sessionStorage.setItem){
          sessionStorage.setItem(key,val);
          sessionStorage.removeItem(key,val);
          supported = true;
        }else{
          supported = false;
        }
      }catch(e){
        supported = false;
      }
      return supported;
  }
};

// 检测是否支持跨域的ajax发送
_.isSupportCors = function () {
  if (typeof window.XMLHttpRequest === 'undefined') {
    return false;
  }
  //Detect browser support for CORS
  if ('withCredentials' in new XMLHttpRequest()) {
    /* supports cross-domain requests */
    return true;
  } else if(typeof XDomainRequest !== "undefined"){
    //Use IE-specific "CORS" code with XDR
    return true;
  } else {
    //Time to retreat with a fallback or polyfill
    return false;
  }
};

_.xhr = function(cors) {
  if (cors) {
    if (typeof window.XMLHttpRequest !== 'undefined' && ("withCredentials" in new XMLHttpRequest())) {
      return new XMLHttpRequest();
    } else if (typeof XDomainRequest !== "undefined") {
      return new XDomainRequest();
    } else {
      return null;
    }
  } else {
    if (typeof window.XMLHttpRequest !== 'undefined') {
      return new XMLHttpRequest();
    }
    if (window.ActiveXObject) {
      try {
        return new ActiveXObject('Msxml2.XMLHTTP')
      } catch (d) {
        try {
          return new ActiveXObject('Microsoft.XMLHTTP')
        } catch (d) {
        }
      }
    }
  }
};


_.ajax = function(para) {
  para.timeout = para.timeout || 20000;

  para.credentials =  (typeof para.credentials) === 'undefined' ? true : para.credentials;
  function getJSON(data) {
    if(!data){
      return '';
    }
    try {
      return JSON.parse(data);
    } catch (e) {
      return {};
    }
  }

  var g = _.xhr(para.cors);

  if(!g) {
    return false;
  }

  if (!para.type) {
    para.type = para.data ? 'POST' : 'GET';
  }
  para = _.extend({
    success: function() {},
    error: function() {}
  }, para);

  try{
    if(typeof g === 'object' && ('timeout' in g)){
      g.timeout = para.timeout;
    }else{
     setTimeout(function(){
        g.abort();
      },para.timeout+500);
    }
  }catch(e){
     try{
       setTimeout(function(){
          g.abort();
        },para.timeout+500);
     }catch(e2){};
  };

  g.onreadystatechange = function() {
    try{
      if (g.readyState == 4) {
        if ((g.status >= 200 && g.status < 300) || g.status == 304) {
          para.success(getJSON(g.responseText));
        } else {
          para.error(getJSON(g.responseText), g.status);
        }
        g.onreadystatechange = null;
        g.onload = null;
      }
    }catch(e){
      g.onreadystatechange = null;
      g.onload = null;
    };

  };

  g.open(para.type, para.url, true);

  try {
    if(para.credentials){
      g.withCredentials = true;
    }
    if (_.isObject(para.header)) {
      for (var i in para.header) {
        g.setRequestHeader(i, para.header[i]);
      }
    }

    if (para.data) {
      if(!para.cors){
        g.setRequestHeader("X-Requested-With", "XMLHttpRequest");
      }
      if (para.contentType === 'application/json') {
        g.setRequestHeader("Content-type", "application/json; charset=UTF-8");
      } else {
        g.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
      }

    }
  } catch (e) {};

  g.send(para.data || null);

};

 _.loadScript = function(para) {
  para = _.extend({
    success: function() {},
    error: function() {},
    appendCall: function(g) {
      document.getElementsByTagName('head')[0].appendChild(g);
    }
  }, para);

    var g = null;
    if (para.type === 'css') {
      g = document.createElement('link');
      g.rel = 'stylesheet';
      g.href = para.url;
    }
    if (para.type === 'js') {
      g = document.createElement('script');
      g.async = 'async';
      g.setAttribute('charset','UTF-8');
      g.src = para.url;
      g.type = 'text/javascript';
    }
    g.onload = g.onreadystatechange = function() {
      if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
        para.success();
        g.onload = g.onreadystatechange = null;
      }
    };
    g.onerror = function() {
      para.error();
      g.onerror = null;
    };
    // if iframe
    para.appendCall(g);
};

_.getHostname = function(url, defaultValue) {
  if (!defaultValue || typeof defaultValue !== "string") {
    defaultValue = "hostname解析异常";
  }
  var hostname = null;
  try {
    hostname = _.URL(url).hostname;
  } catch (e) {
    // do nothing
  }
  return hostname || defaultValue;
};

_.getQueryParamsFromUrl = function(url) {
  var result = {};
  var arr = url.split('?');
  var queryString = arr[1] || '';
  if (queryString) {
    result = _.getURLSearchParams('?' + queryString);
  }
  return result;
};

/**
 * 查询得到URL参数
 * @param {string} queryString - 以问号开头的 query string
 * @return {Object} 一个含有参数列表的key/value对象
 *
 * @example
 * var url = _.getURLSearchParams('?project=testproject&query1=test&silly=willy&field[0]=zero&field[2]=two#test=hash&chucky=cheese');
 *
 * url.project; // => testproject
 */
_.getURLSearchParams = function(queryString) {
  queryString = queryString || "";
  var decodeParam = function(str) {
    return decodeURIComponent(str);
  };
  var args = {};                             // Start with an empty object
  var query = queryString.substring(1);  // Get query string, minus '?'
  var pairs = query.split("&");              // Split at ampersands
  for(var i = 0; i < pairs.length; i++) {    // For each fragment
    var pos = pairs[i].indexOf('=');       // Look for "name=value"
    if (pos === -1) continue;               // If not found, skip it
    var name = pairs[i].substring(0,pos);  // Extract the name
    var value = pairs[i].substring(pos+1); // Extract the value
    name = decodeParam(name);       // Decode the name
    value = decodeParam(value);     // Decode the value
    args[name] = value;                    // Store as a property
  }
  return args;                               // Return the parsed arguments
};

/**
 * 解析URL
 * @param {string} url
 * @return {Object} 一个 URL 对象或者普通JS对象
 *
 * @example
 * var url = _.URL('http://www.domain.com:8080/path/index.html?project=testproject&query1=test&silly=willy&field[0]=zero&field[2]=two#test=hash&chucky=cheese');
 *
 * url.hostname; // => www.domain.com
 * url.searchParams.get('project'); // => testproject
 */
_.URL = function(url) {
  var result = {};
  var basicProps = ['hash', 'host', 'hostname', 'href', 'origin', 'password', 'pathname', 'port', 'protocol', 'search', 'username'];
  // Some browsers allow objects to be created via URL constructor, but instances do not have the expected url properties.
  // See https://www.caniuse.com/#feat=url
  var isURLAPIWorking = function() {
    var url;
    try {
      url = new URL('http://modernizr.com/');
      return url.href === 'http://modernizr.com/';
    } catch (e) {
      return false;
    }
  };
  if (typeof window.URL === 'function' && isURLAPIWorking()) {
    result = new URL(url);
    if (!result.searchParams) {
      result.searchParams = (function(){
        var params = _.getURLSearchParams(result.search);
        return {
          get: function(searchParam) {
            return params[searchParam];
          }
        };
      })();
    }
  } else {
    var _regex = /^https?:\/\/.+/;
    if(_regex.test(url) === false) {
      throw 'Invalid URL';
    }
    var link = document.createElement('a');
    link.href = url;
    for (var i = basicProps.length - 1; i >= 0; i--) {
      var prop = basicProps[i];
      result[prop] = link[prop];
    }
    if (result.hostname && typeof result.pathname === "string" && result.pathname.indexOf('/') !== 0) {
      result.pathname = '/' + result.pathname;
    }
    result.searchParams = (function(){
      var params = _.getURLSearchParams(result.search);
      return {
        get: function(searchParam) {
          return params[searchParam];
        }
      };
    })();
  }
  return result;
};

_.getCurrentDomain = function (url) {
    var sdDomain = sd.para.current_domain;
    switch (typeof (sdDomain)) {
        case "function":
            var resultDomain = sdDomain();
            if (resultDomain === "" || _.trim(resultDomain) === "") {
                return 'url解析失败';
            } else if (resultDomain.indexOf(".")!== -1) {
                return resultDomain;
            } else {
              return "url解析失败";
            }
        case "string":
            if (sdDomain === "" || _.trim(sdDomain) === "") {
                return 'url解析失败';
            } else if (sdDomain.indexOf(".")!== -1) {
                return sdDomain;
            } else {
              return "url解析失败";
            }
        default:
            var cookieTopLevelDomain = _.getCookieTopLevelDomain();
            if (url === '') {
                return 'url解析失败';
            } else if (cookieTopLevelDomain === '') {
                return 'url解析失败';
            } else {
                return cookieTopLevelDomain;
            }
    }
};

_.getCookieTopLevelDomain = function(hostname) {
  hostname = hostname || window.location.hostname;
  /*
  if (hostname === 'localhost') {
    return hostname;
  }
  */
  var splitResult = hostname.split('.');
  if (_.isArray(splitResult) && splitResult.length >= 2 && !/^(\d+\.)+\d+$/.test(hostname)) {
    var domainStr = '.' + splitResult.splice(splitResult.length - 1, 1);
    while (splitResult.length > 0) {
      domainStr = '.' + splitResult.splice(splitResult.length - 1, 1) + domainStr;
      document.cookie = "sensorsdata_domain_test=true; path=/; domain=" + domainStr;
      if (document.cookie.indexOf('sensorsdata_domain_test=true') !== -1) {
        var now = new Date();
        now.setTime(now.getTime() - 1000);
        document.cookie = "sensorsdata_domain_test=true; expires=" + now.toGMTString() + "; path=/; domain=" + domainStr;
        return domainStr;
      }
    }
  }
  return '';
};

_.isReferralTraffic = function(refererstring) {
  refererstring =  refererstring || document.referrer;
  if (refererstring === "") {
    return true;
  }

  return _.getCookieTopLevelDomain(_.getHostname(refererstring)) !== _.getCookieTopLevelDomain();
};


_.ry = function(dom){
  return new _.ry.init(dom);
};
_.ry.init = function(dom){
  this.ele = dom;
};
_.ry.init.prototype = {
  addClass: function(para){
    var classes = ' ' + this.ele.className + ' ';
    if(classes.indexOf(' ' + para + ' ') === -1){
      this.ele.className = this.ele.className + (this.ele.className === '' ? '' : ' ') + para;
    }
    return this;
  },
  removeClass: function(para){
    var classes = ' ' + this.ele.className + ' ';
    if(classes.indexOf(' ' + para + ' ') !== -1){
      this.ele.className = classes.replace(' ' + para + ' ', ' ').slice(1,-1);
    }
    return this;
  },
  hasClass: function(para){
    var classes = ' ' + this.ele.className + ' ';
    if(classes.indexOf(' ' + para + ' ') !== -1){
      return true;
    }else{
      return false;
    }
  },
  attr: function(key,value){
    if(typeof key === 'string' && _.isUndefined(value)){
      return this.ele.getAttribute(key);
    }
    if(typeof key === 'string'){
      value = String(value);
      this.ele.setAttribute(key,value);
    }
    return this;
  },
  offset: function(){
      var rect = this.ele.getBoundingClientRect();
      if ( rect.width || rect.height ) {
        var doc = this.ele.ownerDocument;
        var docElem = doc.documentElement;

        return {
          top: rect.top + window.pageYOffset - docElem.clientTop,
          left: rect.left + window.pageXOffset - docElem.clientLeft
        };
      }else{
        return {
          top: 0,
          left: 0
        }
      }

  },
  getSize: function(){
    if (!window.getComputedStyle) {
      return {width: this.ele.offsetWidth, height: this.ele.offsetHeight};
    }
    try {
      var bounds = this.ele.getBoundingClientRect();
      return {width: bounds.width, height: bounds.height};
    } catch (e){
      return {width: 0, height: 0};
    }
  },
  getStyle: function(value){
    if(this.ele.currentStyle){
      return this.ele.currentStyle[value];
    }else{
      return this.ele.ownerDocument.defaultView.getComputedStyle(this.ele, null).getPropertyValue(value);
    }
  },
  wrap: function(elementTagName){
    var ele = document.createElement(elementTagName);
    this.ele.parentNode.insertBefore(ele, this.ele);
    ele.appendChild(this.ele);
    return _.ry(ele);
  },
  getCssStyle: function(prop){
    var result = this.ele.style.getPropertyValue(prop);
    if (result) {
        return result;
    }
    var rules = null;
    if(typeof window.getMatchedCSSRules === 'function'){
      rules = getMatchedCSSRules(this.ele);
    }
    if(!rules || !_.isArray(rules)){
      return null;
    }
    for (var i = rules.length - 1; i >= 0; i--) {
        var r = rules[i];
        result = r.style.getPropertyValue(prop);
        if (result) {
            return result;
        }
    }
  },
  sibling:function(cur, dir ){
    while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
    return cur;
  },
  next: function() {
    return this.sibling( this.ele, "nextSibling" );
  },
  prev: function( elem ) {
    return this.sibling( this.ele, "previousSibling" );
  },
  siblings: function( elem ) {
    return this.siblings( ( this.ele.parentNode || {} ).firstChild, this.ele);
  },
  children: function( elem ) {
    return this.siblings( this.ele.firstChild );
  },
  parent: function(){
    var parent = this.ele.parentNode;
    parent = parent && parent.nodeType !== 11 ? parent : null;
    return _.ry(parent);
  }
};

_.strToUnicode = function(str){
  if(typeof str !== 'string'){
    sd.log('转换unicode错误',str);
    return str;
  }
  var nstr = '';
  for(var i = 0; i<str.length; i++){
    nstr += '\\'+str.charCodeAt(i).toString(16);
  }
  return nstr;
};

/* _.querySelectorAll = function(val){

  if(typeof val !== 'string'){
    sd.log('选择器错误',val);
    return [];
  }
  // 替换纯数字的id
  var sp = val.split(' ');
  if(sp.length === 1){
    if(/^#\d+/.test(sp[0])){
      val = '#' + _.strToUnicode(sp[0].slice(1));
    }
  }else{
    if(/^#\d+/.test(sp[0])){
      sp[0] = '#' + _.strToUnicode(sp[0].slice(1));
      val = sp.join(' ');
    }
  }

  try{
     return document.querySelectorAll(val);
  }catch(e){
    sd.log('错误',val);
    return [];
  }
}; */

_.getReferrer = function(referrer){
  var referrer = referrer || document.referrer;
  if(typeof referrer !== 'string'){
    return '取值异常_referrer异常_' + String(referrer);
  }
  if (referrer.indexOf("https://www.baidu.com/") === 0) {
    referrer =  referrer.split('?')[0];
  }
  referrer = referrer.slice(0, sd.para.max_referrer_string_length);
  return (typeof referrer === 'string' ? referrer : '' );
};

_.getKeywordFromReferrer = function(referrerUrl){
  referrerUrl = referrerUrl || document.referrer;
  var search_keyword = sd.para.source_type.keyword;
  if(document && typeof referrerUrl === 'string'){
    if(referrerUrl.indexOf('http') === 0) {
      var searchEngine = _.getReferSearchEngine(referrerUrl);
      var query = _.getQueryParamsFromUrl(referrerUrl);
      if (_.isEmptyObject(query)) {
        return '未取到值';
      }
      var temp = null;
      for(var i in search_keyword){
        if (searchEngine === i) {
          if(typeof query === 'object'){
            temp = search_keyword[i];
            if(_.isArray(temp)){
              for(var i = 0; i < temp.length; i++){
                var _value = query[temp[i]];
                if(_value){
                  return _value;
                }
              }
            } else if (query[temp]){
              return query[temp];
            }
          }
        }
      }
      return '未取到值';
    }else{
      if(referrerUrl === ''){
        return '未取到值_直接打开';
      }else{
        return '未取到值_非http的url';
      }
    }
  }else{
    return '取值异常_referrer异常_' + String(referrerUrl);
  }
};

_.getReferSearchEngine = function(referrerUrl) {
  var hostname = _.getHostname(referrerUrl);
  if (!hostname || hostname === 'hostname解析异常') {
    return '';
  }
  var search_keyword = sd.para.source_type.keyword;
  var searchEngineUrls = {
    baidu: [/^.*\.baidu\.com$/],
    bing: [/^.*\.bing\.com$/],
    google: [/^www\.google\.com$/, /^www\.google\.com\.[a-z]{2}$/, /^www\.google\.[a-z]{2}$/],
    sm: [/^m\.sm\.cn$/],
    so: [/^.+\.so\.com$/],
    sogou: [/^.*\.sogou\.com$/],
    yahoo: [/^.*\.yahoo\.com$/]
  };
  for (var prop in searchEngineUrls) {
    var urls = searchEngineUrls[prop];
    for (var i = 0, len = urls.length; i < len; i++) {
      if (urls[i].test(hostname)) {
        return prop;
      }
    }
  }
  return '未知搜索引擎';
};

_.getSourceFromReferrer = function(){
  function getMatchStrFromArr(arr,str){
    for(var i = 0; i<arr.length; i++){
      if(str.split('?')[0].indexOf(arr[i]) !== -1){
        return true;
      }
    }
  }

  var utm_reg='(' + sd.para.source_type.utm.join('|') + ')\\=[^&]+';
  var search_engine = sd.para.source_type.search;
  var social_engine = sd.para.source_type.social;

  var referrer = document.referrer || '';
  var url = _.info.pageProp.url;
  if(url){
    var utm_match = url.match(new RegExp(utm_reg));
    if(utm_match && utm_match[0]){
      return '付费广告流量';
    }else if(getMatchStrFromArr(search_engine,referrer)){
      return '自然搜索流量';
    }else if(getMatchStrFromArr(social_engine,referrer)){
      return '社交网站流量';
    }else if(referrer === ''){
      return '直接流量';
    }else{
      return '引荐流量';
    }
  }else{
    return '获取url异常';
  }
};

_.info = {
  initPage: function() {
    var referrer = _.getReferrer();
    var url = location.href;
    var url_domain = _.getCurrentDomain(url);
    if(!url_domain){
      // TODO
      //_.jssdkDebug('url_domain异常_'+ url + '_' + url_domain);
      sd.debug.jssdkDebug('url_domain异常_'+ url + '_' + url_domain);
    }

    this.pageProp = {
      referrer: referrer,
      referrer_host: referrer ? _.getHostname(referrer) : "",
      url: url,
      url_host: _.getHostname(url, 'url_host取值异常'),
      url_domain: url_domain
    };


  },
  //当前页面的一些属性，在store初始化是生成
  pageProp: {},

  campaignParams: function() {
    var campaign_keywords = sd.source_channel_standard.split(' ')
      , kw = ''
      , params = {};
    if (_.isArray(sd.para.source_channel) && sd.para.source_channel.length > 0) {
      campaign_keywords = campaign_keywords.concat(sd.para.source_channel);
      campaign_keywords = _.unique(campaign_keywords);
    }
    _.each(campaign_keywords, function(kwkey) {
      kw = _.getQueryParam(location.href, kwkey);
      if (kw.length) {
        params[kwkey] = kw;
      }
    });

    return params;
  },
  campaignParamsStandard: function(prefix,prefix_add) {
    prefix = prefix || '';
    prefix_add = prefix_add || '';
    var utms = _.info.campaignParams();
    var $utms = {}, otherUtms = {};
    for (var i in utms) {
      if ((' ' + sd.source_channel_standard + ' ').indexOf(' ' + i + ' ') !== -1) {
        $utms[prefix + i] = utms[i];
      } else {
        otherUtms[prefix_add + i] = utms[i];
      }
    }
    return {
      $utms: $utms,
      otherUtms: otherUtms
    };
  },
  // 预置属性
  properties: function() {
    return {
      $screen_height: Number(screen.height) || 0,
      $screen_width: Number(screen.width) || 0,
      // 我说两遍写的重复，佳捷说就写两遍
      $lib: 'js',
      $lib_version: String(sd.lib_version)
    };
  },
  // 保存临时的一些变量，只针对当前页面有效
  currentProps: {},
  register: function(obj) {
    _.extend(_.info.currentProps, obj);
  }
};




// 发送队列
_.autoExeQueue = function(){
  var queue = {
    // 简单队列
    items : [],
    enqueue: function(val){
      this.items.push(val);
      this.start();
    },
    dequeue: function(){
      return this.items.shift();
    },
    getCurrentItem: function(){
      return this.items[0];
    },
    // 自动循环执行队列
    isRun: false,
    start: function(){
      if(this.items.length > 0 && !this.isRun){
        this.isRun = true;
        this.getCurrentItem().start();
      }
    },
    close: function(){
      this.dequeue();
      this.isRun = false;
      this.start();
    }
  };
  return queue;
};



  _.trackLink = function(obj,event_name,event_prop){
    obj = obj || {};
    var link = null;
    if(obj.ele){
      link = obj.ele;
    }
    if(obj.event){
      if(obj.target){
         link = obj.target;
       }else{
         link = obj.event.target;
       }
    }

    event_prop = event_prop || {};
    if(!link || (typeof link !== 'object')){
      return false;
    }
    // 如果是非当前页面会跳转的链接，直接track
    if (!link.href || /^javascript/.test(link.href) || link.target || link.download || link.onclick) {
      sd.track(event_name, event_prop);
      return false;
    }
    function linkFunc(e){
      e.stopPropagation();
      e.preventDefault();   // 阻止默认跳转
      var hasCalled = false;
      function track_a_click(){
        if (!hasCalled) {
          hasCalled = true;
          location.href = link.href;  //把 A 链接的点击跳转,改成 location 的方式跳转
        }
      }
      setTimeout(track_a_click, 1000);  //如果没有回调成功，设置超时回调
      sd.track(event_name, event_prop, track_a_click); //把跳转操作加在callback里
    }
    if(obj.event){
      linkFunc(obj.event);
    }
    if(obj.ele){
      _.addEvent(obj.ele,'click',function(e){
        linkFunc(e);
      });
    }
  };




})();


/*
sd的各个方法，包含sdk的一些基本功能
*/


 sd.para_default = {
  preset_properties: {
    latest_utm: true,
    latest_traffic_source_type: true,
    latest_search_keyword: true,
    latest_referrer: true,
    latest_referrer_host: false,
    latest_landing_page: false,
    url: false,
    title: false
  },
  img_use_crossorigin: false,
    //scrollmap:{delay:6000}

    name: 'sa',
    // referrer字符串截取
    max_referrer_string_length: 200,
    //通用字符串截取，超过7000的字符串会导致url超长发不出去，所以限制长度
    max_string_length: 500,
    //    send_error_event: true,
    cross_subdomain: true,
    show_log: true,
    is_debug: false,
    debug_mode: false,
    debug_mode_upload: false,

    // todo 前端session时长
    session_time: 0,

    use_client_time: false,
    //来源参数名字
    source_channel: [],

    send_type: 'image',

    // 七鱼过滤id
    vtrack_ignore: {},

    auto_init: true,

    is_track_single_page: false,

    is_single_page: false,

    batch_send: false,

    // 如果要设置，设置数组
    source_type: {},
    callback_timeout: 200,
    datasend_timeout: 3000,
    queue_timeout: 300,
    is_track_device_id: false,
    use_app_track_config:{
      use_app_track: false,
      use_app_track_is_send: true,
      white_list:[],
      H5verify:false
    },
    ignore_oom: true
  };

sd.addReferrerHost = function(data) {
  var defaultHost = "取值异常";
  if (_.isObject(data.properties)) {
    if (data.properties.$first_referrer) {
      data.properties.$first_referrer_host = _.getHostname(data.properties.$first_referrer, defaultHost);
    }
    if (data.type === "track") {
      if ('$referrer' in data.properties) {
        data.properties.$referrer_host = data.properties.$referrer === "" ? "" : _.getHostname(data.properties.$referrer, defaultHost);
      }
      if (sd.para.preset_properties.latest_referrer && sd.para.preset_properties.latest_referrer_host) {
        data.properties.$latest_referrer_host = data.properties.$latest_referrer === "" ? "" : _.getHostname(data.properties.$latest_referrer, defaultHost);
      }
    }
  }
};

sd.addPropsHook = function(data) {
  if (sd.para.preset_properties && sd.para.preset_properties.url && data.type === "track" && typeof data.properties.$url === 'undefined') {
    data.properties.$url = window.location.href;
  }
  if (sd.para.preset_properties && sd.para.preset_properties.title && data.type === "track" && typeof data.properties.$title === 'undefined') {
    data.properties.$title = document.title;
  }
};

sd.initPara = function(para){
    // 默认配置
  sd.para = para || sd.para || {};
  var latestObj = {};
  if(_.isObject(sd.para.is_track_latest)){
    for (var latestProp in sd.para.is_track_latest) {
      latestObj['latest_' + latestProp] = sd.para.is_track_latest[latestProp];
    }
  }

  sd.para.preset_properties = _.extend({}, sd.para_default.preset_properties, latestObj, sd.para.preset_properties || {});
  var app_track_config = {};
  if(sd.para.use_app_track){
    app_track_config.use_app_track = sd.para.use_app_track;
  }
  if(sd.para.use_app_track_is_send){
    app_track_config.use_app_track_is_send = sd.para.use_app_track_is_send;  
  }
  sd.para.use_app_track_config = _.extend({},sd.para_default.use_app_track_config,app_track_config,sd.para.use_app_track_config || {})

  var i;
  // 合并配置
  for (i in sd.para_default) {
    if (sd.para[i] === void 0) {
      sd.para[i] = sd.para_default[i];
    }
  }
  // 修复没有配置协议的问题，自动取当前页面的协议
  if(typeof sd.para.server_url === 'string' && sd.para.server_url.slice(0,3) === '://'){
    sd.para.server_url = location.protocol + sd.para.server_url;
  }
  if(typeof sd.para.web_url === 'string' && sd.para.web_url.slice(0,3) === '://'){
    sd.para.web_url = location.protocol + sd.para.web_url;
  }

  if(sd.para.send_type !== 'image' && sd.para.send_type !== 'ajax' && sd.para.send_type !== 'beacon'){
    sd.para.send_type = 'image';
  }

  var batch_send_default = {
    datasend_timeout: 6000,
    send_interval: 6000,
    one_send_max_length: 6
  };

  if(_.localStorage.isSupport() && _.isSupportCors() && typeof localStorage === 'object'){
    if (sd.para.batch_send === true) {
      sd.para.batch_send = _.extend({}, batch_send_default);
      sd.para.use_client_time = true;
    }else if(typeof sd.para.batch_send === 'object'){
      sd.para.use_client_time = true;
      sd.para.batch_send = _.extend({}, batch_send_default, sd.para.batch_send);
    }
  }else{
    sd.para.batch_send = false;
  }


  var utm_type = ['utm_source','utm_medium','utm_campaign','utm_content','utm_term'];
  var search_type = ['www.baidu.','m.baidu.','m.sm.cn','so.com','sogou.com','youdao.com','google.','yahoo.com/','bing.com/','ask.com/'];
  var social_type = ['weibo.com','renren.com','kaixin001.com','douban.com','qzone.qq.com','zhihu.com','tieba.baidu.com','weixin.qq.com'];
  var search_keyword = {baidu:['wd','word','kw','keyword'],google:'q',bing:'q',yahoo:'p',sogou:['query','keyword'],so:'q',sm:'q'};

  if(typeof sd.para.source_type === 'object'){
    sd.para.source_type.utm = _.isArray(sd.para.source_type.utm) ? sd.para.source_type.utm.concat(utm_type) : utm_type;
    sd.para.source_type.search = _.isArray(sd.para.source_type.search) ? sd.para.source_type.search.concat(search_type) : search_type;
    sd.para.source_type.social = _.isArray(sd.para.source_type.social) ? sd.para.source_type.social.concat(social_type) : social_type;
    sd.para.source_type.keyword = _.isObject(sd.para.source_type.keyword) ?  _.extend(search_keyword,sd.para.source_type.keyword) : search_keyword;
  }

  if(_.isObject(sd.para.heatmap)) {
    sd.para.heatmap.clickmap = sd.para.heatmap.clickmap || 'default';
    sd.para.heatmap.scroll_notice_map = sd.para.heatmap.scroll_notice_map || 'default';
    sd.para.heatmap.scroll_delay_time = sd.para.heatmap.scroll_delay_time || 4000;
    sd.para.heatmap.scroll_event_duration = sd.para.heatmap.scroll_event_duration || 18000; // The max value of $event_duration property for $WebStay event, in seconds (5 Hours).
    sd.para.heatmap.renderRefreshTime = sd.para.heatmap.renderRefreshTime || 1000;
    sd.para.heatmap.loadTimeout = sd.para.heatmap.loadTimeout || 1000;
  }
  // 优化配置
  if(typeof sd.para.server_url === 'object' && sd.para.server_url.length){
    for(i = 0; i < sd.para.server_url.length; i++){
      if (!/sa\.gif[^\/]*$/.test(sd.para.server_url[i])) {
        sd.para.server_url[i] = sd.para.server_url[i].replace(/\/sa$/, '/sa.gif').replace(/(\/sa)(\?[^\/]+)$/, '/sa.gif$2');
      }
    }
  }else if (!/sa\.gif[^\/]*$/.test(sd.para.server_url)) {
    sd.para.server_url = sd.para.server_url.replace(/\/sa$/, '/sa.gif').replace(/(\/sa)(\?[^\/]+)$/, '/sa.gif$2');
  }
  if(typeof sd.para.server_url === 'string'){
    sd.para.debug_mode_url = sd.para.debug_mode_url || sd.para.server_url.replace('sa.gif', 'debug');
  }
  // 是否需要非cache，等于每次请求文件
  if (sd.para.noCache === true) {
    sd.para.noCache = '?' + (new Date()).getTime();
  } else {
    sd.para.noCache = '';
  }

  if (sd.para.callback_timeout > sd.para.datasend_timeout) {
    sd.para.datasend_timeout = sd.para.callback_timeout;
  }
  if (sd.para.callback_timeout > sd.para.queue_timeout) {
    sd.para.queue_timeout = sd.para.callback_timeout;
  }
  if (sd.para.queue_timeout > sd.para.datasend_timeout) {
    sd.para.datasend_timeout = sd.para.queue_timeout;
  }

};


// sa 当前状态管理
sd.readyState = {
  state : 0,
  historyState:[],
  stateType: {
    '1': '1-init未开始',
    '2': '2-init开始',
    '3': '3-store完成'
  },
  getState : function(){
    return this.historyState.join('\n');
  },
  setState:function(n){
    if(String(n) in this.stateType){
      this.state = n;
    }
    this.historyState.push(this.stateType[n]);
  }
};


sd.setPreConfig = function(sa){
  sd.para = sa.para;
  sd._q = sa._q;
};


sd.setInitVar= function(){
  sd._t = sd._t || 1 * new Date();
  sd.lib_version = '1.14.23';
  sd.is_first_visitor = false;
  // 标准广告系列来源
  sd.source_channel_standard = 'utm_source utm_medium utm_campaign utm_content utm_term';
};

sd.log = function() {
  if((_.sessionStorage.isSupport() && sessionStorage.getItem('sensorsdata_jssdk_debug') === 'true') || sd.para.show_log){

    if(sd.para.show_log === true || sd.para.show_log === 'string' || sd.para.show_log === false){
      arguments[0] = _.formatJsonString(arguments[0]);
    }

    if (typeof console === 'object' && console.log) {
      try {
        return console.log.apply(console, arguments);
      } catch (e) {
        console.log(arguments[0]);
      }
    }
  }
};

/**
 * 设置 sessionStorage 开启本地控制台日志输出
 */
sd.enableLocalLog = function() {
  if(_.sessionStorage.isSupport()) {
    try {
      sessionStorage.setItem('sensorsdata_jssdk_debug', 'true');
    } catch (e) {
      sd.log('enableLocalLog error: ' + e.message);
      // handle the exception here.
    }
  }
};

/**
 * 删除 sessionStorage 关闭本地控制台日志输出
 */
sd.disableLocalLog = function() {
  if(_.sessionStorage.isSupport()) {
    sessionStorage.removeItem('sensorsdata_jssdk_debug');
  }
};

sd.debug = {
  distinct_id: function() {
    /*
    var relation = {
      'e-0': '未知错误',
      'e-1': 'toState()传值的数据中缺少distinct_id，SDK自动分配distinct_id',
      'e-2': 'toState()传值的数据不是一个有效的JSON字符串，SDK自动分配distinct_id'
    };
    var debug_info = relation['e-' + key] || relation['e-0'];
    if (sd.para.is_debug === true || sd.para.is_debug.distinct_id === true) {
      sd.log(debug_info);
      this._sendDebug('distinct_id-' + key + '-' + JSON.stringify(data));
    }
    */
  },
  jssdkDebug: function() {
    /*
    if (sd.para.is_debug === true) {
      if(_.isString(recevie_prop)){
        this._sendDebug(recevie_prop);
      }else{
        var cookie = store.getCookieName();
        var match = document.cookie.match(new RegExp(cookie + '[^;]+'));
        if(match && match[0] ){
          cookie = match[0];
        }else{
          cookie = '';
        }
        recevie_prop._jssdk_debug_info = '(' + cookie + ')' + navigator.userAgent;
        this._sendDebug(JSON.stringify(recevie_prop));
      }
    }
    */
  },
  _sendDebug: function(debugString) {
    sd.track('_sensorsdata2019_debug', {
      _jssdk_debug_info: debugString
    });
  },
  apph5: function(obj){
    var name = 'app_h5打通失败-';
    var relation = {
      '1': name + 'use_app_track为false',
      '2': name + 'Android或者iOS，没有暴露相应方法',
      '3.1': name + 'Android校验server_url失败',
      '3.2': name + 'iOS校验server_url失败',
      '4.1': name + 'H5 校验 iOS server_url 失败',
      '4.2': name + 'H5 校验 Android server_url 失败',
      '4.3': name + 'H5 数据成功发往 Android',
      '4.4': name + 'H5 数据成功发往 iOS'
    };
    var output = obj.output;
    var step = obj.step;
    var data = obj.data || '';

    // 控制台输出
    if(output === 'all' || output === 'console'){
      sd.log(relation[step]);
    }
    if((output === 'all' || output === 'code') && _.isObject(sd.para.is_debug) && sd.para.is_debug.apph5){
      if (!data.type || data.type.slice(0, 7) !== 'profile') {
        data.properties._jssdk_debug_info = 'apph5-' + String(step);
      }
    }
  }
};

  var commonWays = {
    setOnlineState:function(state){
      if(state === true && _.isObject(sd.para.jsapp) && typeof sd.para.jsapp.getData === 'function'){
        sd.para.jsapp.isOnline = true;
        var arr = sd.para.jsapp.getData();
        if(_.isArray(arr) && arr.length > 0){
          _.each(arr,function(str){
            if(_.isJSONString(str)){
              sd.sendState.pushSend(JSON.parse(str));
            }
          });
        }
      }else{
        sd.para.jsapp.isOnline = false;
      }
    },
    autoTrackIsUsed:false,
    isReady:function(callback){
      callback();
    },
    // 获取谷歌标准参数
    getUtm: function() {
      return _.info.campaignParams();
    },
    // 获取当前页面停留时间
    getStayTime: function() {
      return ((new Date()) - sd._t) / 1000;
    },
    setProfileLocal: function(obj){
      if(!_.localStorage.isSupport()){
        sd.setProfile(obj);
        return false;
      }
      if(!_.isObject(obj) || _.isEmptyObject(obj)){
        return false;
      }
      var saveData = _.localStorage.parse('sensorsdata_2015_jssdk_profile');
      var isNeedSend = false;
      if(_.isObject(saveData) && !_.isEmptyObject(saveData)){
        for(var i in obj){
          if((i in saveData && saveData[i] !== obj[i]) || !(i in saveData)){
            saveData[i] = obj[i];
            isNeedSend = true;
          }
        }
        if(isNeedSend){
          _.localStorage.set('sensorsdata_2015_jssdk_profile',JSON.stringify(saveData));
          sd.setProfile(obj);
        }
      }else{
        _.localStorage.set('sensorsdata_2015_jssdk_profile',JSON.stringify(obj));
        sd.setProfile(obj);
      }
     },
    //set init referrer
    setInitReferrer: function() {
      var _referrer = _.getReferrer();
      sd.setOnceProfile({
        _init_referrer: _referrer,
        _init_referrer_host: _.info.pageProp.referrer_host
      });
    },
    // set init sessionRegister cookie
    setSessionReferrer: function() {
      var _referrer = _.getReferrer();
      store.setSessionPropsOnce({
        _session_referrer: _referrer,
        _session_referrer_host: _.info.pageProp.referrer_host
      });
    },
    // set default referrr and pageurl
    setDefaultAttr: function() {
      _.info.register({
        _current_url: location.href,
        _referrer: _.getReferrer(),
        _referring_host: _.info.pageProp.referrer_host
      });
    },
    trackHeatMap: function(target, props, callback){
      if((typeof target === 'object') && target.tagName){
        var tagName = target.tagName.toLowerCase();
        var parent_ele = target.parentNode.tagName.toLowerCase();
        if (tagName !== 'button' && tagName !== 'a' && parent_ele !== 'a' && parent_ele !== 'button' && tagName !== 'input' && tagName !== 'textarea' && !_.hasAttribute(target, 'data-sensors-click')) {
          heatmap.start(null,target,tagName, props, callback);
        }
      }
    },
    trackAllHeatMap: function(target, props, callback){
      if((typeof target === 'object') && target.tagName){
        var tagName = target.tagName.toLowerCase();
        heatmap.start(null,target,tagName, props, callback);
      }
    },
    autoTrackSinglePage:function(para,callback){
      if(this.autoTrackIsUsed){
        var url = _.info.pageProp.url;
      }else{
        var url = _.info.pageProp.referrer;
      }
      para = _.isObject(para) ? para : {};

      para = _.isObject(para) ? para : {};


      function getUtm(){
        var utms = _.info.campaignParams();
        var $utms = {};
        for (var i in utms) {
          if ((' ' + sd.source_channel_standard + ' ').indexOf(' ' + i + ' ') !== -1) {
            $utms['$' + i] = utms[i];
          } else {
            $utms[i] = utms[i];
          }
        }
        return $utms;
      }

      if (sd.is_first_visitor && !para.not_set_profile) {
        sd.setOnceProfile(_.extend({
          // 暂时隐藏，等extractor都部署上去 $first_landing_page: _.info.pageProp.url.slice(0, sd.para.max_referrer_string_length),
          $first_visit_time: new Date(),
          $first_referrer: _.getReferrer(),
          $first_browser_language: navigator.language || '取值异常',
          $first_browser_charset: typeof document.charset === 'string' ? document.charset.toUpperCase() : '取值异常',
          $first_traffic_source_type: _.getSourceFromReferrer(),
          $first_search_keyword: _.getKeywordFromReferrer()
        }, getUtm())
        );
        sd.is_first_visitor = false;
      }
      if(para.not_set_profile){
        delete para.not_set_profile;
      }


      function closure(p,c){
        sd.track('$pageview', _.extend({
            $referrer: url,
            $url: location.href,
            $url_path: location.pathname,
            $title: document.title
          }, p, getUtm()),c
        );
        url = location.href;
      }
      closure(para,callback);
      this.autoTrackSinglePage = closure;
    },
    autoTrackWithoutProfile: function(para,callback){
      para = _.isObject(para) ? para : {};
      this.autoTrack(_.extend(para,{not_set_profile:true}),callback);
    },
    autoTrack: function(para, callback) {
      para = _.isObject(para) ? para : {};

      var utms = _.info.campaignParams();
      var $utms = {};
      for (var i in utms) {
        if ((' ' + sd.source_channel_standard + ' ').indexOf(' ' + i + ' ') !== -1) {
          $utms['$' + i] = utms[i];
        } else {
          $utms[i] = utms[i];
        }
      }
      // setOnceProfile 如果是新用户，且允许设置profile
      if (sd.is_first_visitor && !para.not_set_profile) {
        sd.setOnceProfile(_.extend({
            // 暂时隐藏，等extractor都部署上去 $first_landing_page: _.info.pageProp.url.slice(0, sd.para.max_referrer_string_length),
            $first_visit_time: new Date(),
            $first_referrer: _.getReferrer(),
            $first_browser_language: navigator.language || '取值异常',
            $first_browser_charset: typeof document.charset === 'string' ? document.charset.toUpperCase() : '取值异常',
            $first_traffic_source_type: _.getSourceFromReferrer(),
            $first_search_keyword: _.getKeywordFromReferrer()
          }, $utms)
        );
        sd.is_first_visitor = false;
      }
      if(para.not_set_profile){
        delete para.not_set_profile;
      }

      // 解决单页面的referrer问题
      var current_page_url = location.href;

      if(sd.para.is_single_page){
        _.addHashEvent(function(){
          var referrer = _.getReferrer(current_page_url);
          sd.track('$pageview', _.extend({
              $referrer: referrer,
              $url: location.href,
              $url_path: location.pathname,
              $title: document.title
            }, $utms, para),callback
          );
          current_page_url = location.href;
        });
      }

      sd.track('$pageview', _.extend({
          $referrer: _.getReferrer(),
          $url: location.href,
          $url_path: location.pathname,
          $title: document.title
        }, $utms, para),callback
      );
      this.autoTrackIsUsed=true;
    },
    getAnonymousID:function(){
      if(_.isEmptyObject(sd.store._state)){
        return '请先初始化SDK';
      }else{
        // 优先使用临时属性
        return sd.store._state._first_id || sd.store._state.first_id || sd.store._state._distinct_id || sd.store._state.distinct_id;
      }
    }


  };

  // 一些常见的方法
  sd.quick = function() {
    var arg = Array.prototype.slice.call(arguments);
    var arg0 = arg[0];
    var arg1 = arg.slice(1);
    if (typeof arg0 === 'string' && commonWays[arg0]) {
      return commonWays[arg0].apply(commonWays, arg1);
    } else if (typeof arg0 === 'function') {
      arg0.apply(sd, arg1);
    } else {
      sd.log('quick方法中没有这个功能' + arg[0]);
    }
  };


  /*
   * @param {string} event
   * @param {string} properties
   * */
  sd.track = function(e, p, c) {
    if (saEvent.check({event: e, properties: p})) {
      saEvent.send({
        type: 'track',
        event: e,
        properties: p
      }, c);
    }
  };

  sd.trackLink = function(link,event_name,event_prop){
    if(typeof link === 'object' && link.tagName){
      _.trackLink({ele:link},event_name,event_prop);
    }else if(typeof link === 'object' && link.target && link.event){
      _.trackLink(link,event_name,event_prop);
    }
  };
    // 跟踪链接
  sd.trackLinks = function(link,event_name,event_prop){
    var ele = link;
    event_prop = event_prop || {};
    if(!link || (typeof link !== 'object')){
      return false;
    }
    if (!link.href || /^javascript/.test(link.href) || link.target) {
      return false;
    }
    _.addEvent(link,'click',function(e){
      e.preventDefault();   // 阻止默认跳转
      var hasCalled = false;
      setTimeout(track_a_click, 1000);  //如果没有回调成功，设置超时回调
      function track_a_click(){
        if (!hasCalled) {
          hasCalled = true;
          location.href = link.href;  //把 A 链接的点击跳转,改成 location 的方式跳转
        }
      }
      sd.track(event_name, event_prop, track_a_click); //把跳转操作加在callback里
    });

  };


  /*
   * @param {object} properties
   * */
  sd.setProfile = function(p, c) {
    if (saEvent.check({propertiesMust: p})) {
      saEvent.send({
        type: 'profile_set',
        properties: p
      }, c);
    }
  };

  sd.setOnceProfile = function(p, c) {
    if (saEvent.check({propertiesMust: p})) {
      saEvent.send({
        type: 'profile_set_once',
        properties: p
      }, c);
    }
  };

  /*
   * @param {object} properties
   * */
  sd.appendProfile = function(p, c) {
    if (saEvent.check({propertiesMust: p})) {
      _.each(p, function(value, key) {
        if (_.isString(value)) {
          p[key] = [value];
        } else if (_.isArray(value)) {

        } else {
          delete p[key];
          sd.log('appendProfile属性的值必须是字符串或者数组');
        }
      });
      if (!_.isEmptyObject(p)) {
        saEvent.send({
          type: 'profile_append',
          properties: p
        }, c);
      }
    }
  };
  /*
   * @param {object} properties
   * */
  sd.incrementProfile = function(p, c) {
    var str = p;
    if (_.isString(p)) {
      p = {}
      p[str] = 1;
    }
    function isChecked(p) {
      for (var i in p) {
        if (!/-*\d+/.test(String(p[i]))) {
          return false;
        }
      }
      return true;
    }

    if (saEvent.check({propertiesMust: p})) {
      if (isChecked(p)) {
        saEvent.send({
          type: 'profile_increment',
          properties: p
        }, c);
      } else {
        sd.log('profile_increment的值只能是数字');
      }
    }
  };

  sd.deleteProfile = function(c) {
    saEvent.send({
      type: 'profile_delete'
    }, c);
    store.set('distinct_id', _.UUID());
    store.set('first_id','');
  };
  /*
   * @param {object} properties
   * */
  sd.unsetProfile = function(p, c) {
    var str = p;
    var temp = {};
    if (_.isString(p)) {
      p = [];
      p.push(str);
    }
    if (_.isArray(p)) {
      _.each(p, function(v) {
        if (_.isString(v)) {
          temp[v] = true;
        } else {
          sd.log('profile_unset给的数组里面的值必须时string,已经过滤掉', v);
        }
      });
      saEvent.send({
        type: 'profile_unset',
        properties: temp
      }, c);
    } else {
      sd.log('profile_unset的参数是数组');
    }
  };
  /*
   * @param {string} distinct_id
   * */
  sd.identify = function(id, isSave) {
    if(typeof id === 'number'){
      id = String(id);
    }
    var firstId = store.getFirstId();
    if (typeof id === 'undefined') {
      if(firstId){
        store.set('first_id', _.UUID());
      }else{
        store.set('distinct_id', _.UUID());
      }
    } else if (saEvent.check({distinct_id: id})) {
      if (isSave === true) {
        if(firstId){
          store.set('first_id', id);
        }else{
          store.set('distinct_id', id);
        }
      } else {
        if(firstId){
          store.change('first_id', id);
        }else{
          store.change('distinct_id', id);
        }
      }

    } else {
      sd.log('identify的参数必须是字符串');
    }
  };
  /*
   * 这个接口是一个较为复杂的功能，请在使用前先阅读相关说明:http://www.sensorsdata.cn/manual/track_signup.html，并在必要时联系我们的技术支持人员。
   * @param {string} distinct_id
   * @param {string} event
   * @param {object} properties
   * */
  sd.trackSignup = function(id, e, p, c) {
    if (saEvent.check({distinct_id: id, event: e, properties: p})) {
      var original_id = store.getFirstId() || store.getDistinctId();
      store.set('distinct_id', id);
      saEvent.send({
        original_id: original_id,
        distinct_id: id,
        type: 'track_signup',
        event: e,
        properties: p
      }, c);
    }
  };

  /*
   * @param {string} testid
   * @param {string} groupid
   * */
  sd.trackAbtest = function(t, g) {
    /*
     if (saEvent.check({test_id: t, group_id: g})) {
     saEvent.send({
     type: 'track_abtest',
     properties: {
     test_id: t,
     group_id: g
     }
     });
     }*/
  };

  /**
   * Register a set of properties, which are included with all events.
   *
   * @param {Object} obj - An object of properties to be registered.
   *
   */
  sd.registerPage = function(obj) {
    if (saEvent.check({properties: obj})) {
      _.extend(_.info.currentProps, obj);
    } else {
      sd.log('register输入的参数有误');
    }
  };

  sd.clearAllRegister = function(arr){
    store.clearAllProps(arr);
  };

  sd.register = function(props) {
    if (saEvent.check({properties: props})) {
      store.setProps(props);
    } else {
      sd.log('register输入的参数有误');
    }
  };

  sd.registerOnce = function(props) {
    if (saEvent.check({properties: props})) {
      store.setPropsOnce(props);
    } else {
      sd.log('registerOnce输入的参数有误');
    }
  };

  sd.registerSession = function(props) {
    if (saEvent.check({properties: props})) {
      store.setSessionProps(props);
    } else {
      sd.log('registerSession输入的参数有误');
    }
  };

  sd.registerSessionOnce = function(props) {
    if (saEvent.check({properties: props})) {
      store.setSessionPropsOnce(props);
    } else {
      sd.log('registerSessionOnce输入的参数有误');
    }
  };

  sd.login = function(id, callback){
    if(typeof id === 'number'){
      id = String(id);
    }
    if (saEvent.check({distinct_id: id})) {
      var firstId = store.getFirstId();
      var distinctId = store.getDistinctId();
      if(id !== distinctId){
        if(!firstId){
          store.set('first_id',distinctId);
        }
        sd.trackSignup(id,'$SignUp', {}, callback);
      } else {
        callback && callback();
      }
    } else {
      sd.log('login的参数必须是字符串');
      callback && callback();
    }
  };

  sd.logout = function(isChangeId){
    var firstId = store.getFirstId();
    if(firstId){
      store.set('first_id','');
      if(isChangeId === true){
        store.set('distinct_id',_.UUID());
      }else{
        store.set('distinct_id',firstId);
      }
    }else{
      sd.log('没有first_id，logout失败');
    }
  };

  sd.getPresetProperties = function(){
    function getUtm(){
       var utms = _.info.campaignParams();
       var $utms = {};
       for (var i in utms) {
         if ((' ' + sd.source_channel_standard + ' ').indexOf(' ' + i + ' ') !== -1) {
           $utms['$' + i] = utms[i];
         } else {
           $utms[i] = utms[i];
         }
       }
       return $utms;
     }

    var obj = {
      $referrer: _.info.pageProp.referrer || '',
      $referrer_host: _.info.pageProp.referrer ? _.getHostname(_.info.pageProp.referrer) : '',
      $url: location.href,
      $url_path: location.pathname,
      $title: document.title || '',
      _distinct_id: store.getDistinctId()
    };
    var result = _.extend({}, _.info.properties(),sd.store.getProps(),getUtm(),obj);
    if (sd.para.preset_properties.latest_referrer && sd.para.preset_properties.latest_referrer_host) {
      result.$latest_referrer_host = result.$latest_referrer === "" ? "" : _.getHostname(result.$latest_referrer);
    }
    return result;
  };


/*
数据处理和发送的流程
数据批量发送
*/

function BatchSend(){
    //正在发送中的数据，如果后续还有，就不发送
  this.sendingData = 0;
};

BatchSend.prototype = {
  add : function(data){
    if(_.isObject(data)){
      this.writeStore(data);
      if(data.type === 'track_signup' || data.event === '$pageview'){
        this.sendStrategy();
      }
    }
  },
  remove: function(keys){
    var me = this;
    if(this.sendingData > 0){
      --this.sendingData;
    }
    if(_.isArray(keys) && keys.length > 0){
      _.each(keys,function(key){
        _.localStorage.remove(key);
      });
    }
  },
  send: function(data){
    var me = this;
    var server_url = _.isArray(sd.para.server_url) ? sd.para.server_url[0] : sd.para.server_url;
    _.ajax({
      url: server_url,
      type: 'POST',
      data: 'data_list=' + encodeURIComponent(_.base64Encode(JSON.stringify(data.vals))),
      credentials: false,
      timeout: sd.para.batch_send.datasend_timeout,
      cors: true,
      success:function(){
        me.remove(data.keys);
      },
      error: function() {
        if (me.sendingData > 0) {
          --me.sendingData;
        }
      }
    });

  },
  sendPrepare:function(data){
    var arr = data.vals;
    //优化最大发送数据量
    var maxLen = sd.para.batch_send.one_send_max_length;
    var arrLen = arr.length;
    if(arrLen > 0){
      if(arrLen <= maxLen){
        this.send({
          keys: data.keys,
          vals: arr
        });
      }else{
        for(var i = 0; i*maxLen < arrLen; i++){
           this.send({
            keys: data.keys.splice(0,maxLen),
            vals: arr.splice(0,maxLen)
          });
        }

      }
    }
  },
  sendStrategy:function(){
  //定时发送和次数发送，都存在页面打开没及时发送的问题，所以优化如果是profile和pageview则触发发送
  //次数发送存在不满足次数，永远发送不了的问题，所以选择定时发送
    var data = this.readStore();
    if(data.keys.length > 0 && this.sendingData === 0){
      this.sendingData = Math.ceil(data.vals.length/sd.para.batch_send.one_send_max_length)
      this.sendPrepare(data);
    }
  },
  batchInterval:function(){
    var me = this;
    setInterval(function(){
      me.sendStrategy();
    },sd.para.batch_send.send_interval);
  },
  readStore:function(){
    var keys = [];
    var vals = [];
    var obj = {};
    var val = null;
    var now = (new Date()).getTime();
    var len = localStorage.length;
    for (var i = 0; i < len; i++) {
      var key = localStorage.key(i);
      if(key.indexOf('sawebjssdk-') === 0 && /^sawebjssdk\-\d+$/.test(key)){
        val = localStorage.getItem(key);
        if(val){
          val = _.safeJSONParse(val);
          if(val && _.isObject(val)){
            val._flush_time = now;
            keys.push(key);
            vals.push(val);
          }else{
            localStorage.removeItem(key);
            sd.log('localStorage-数据parse异常' + val);
          }
        }else{
          localStorage.removeItem(key);
          sd.log('localStorage-数据取值异常' + val);
        }
      }
    }
    return {
      keys:keys,
      vals:vals
    };
  },
  writeStore:function(data){
    var uuid = String(Math.random()).slice(2,5)+String(Math.random()).slice(2,5)+String((new Date()).getTime()).slice(3);
    localStorage.setItem('sawebjssdk-'+uuid,JSON.stringify(data));
  }
};

sd.batchSend = new BatchSend();




// 各种发送方式
var dataSend = {};

dataSend.getSendUrl = function(url,data){
  var base64Data = _.base64Encode(data);
  var crc = 'crc=' + _.hashCode(base64Data);
  if (url.indexOf('?') !== -1) {
    return url + '&data=' + encodeURIComponent(base64Data) + '&ext=' + encodeURIComponent(crc);
  }else{
    return url + '?data=' + encodeURIComponent(base64Data) + '&ext=' + encodeURIComponent(crc);
  }
};

dataSend.getSendData = function(data){
  var base64Data = _.base64Encode(data);
  var crc = 'crc=' + _.hashCode(base64Data);
  return 'data=' + encodeURIComponent(base64Data) + '&ext=' + encodeURIComponent(crc);
};


dataSend.getInstance = function(data){
  var sendType = this.getSendType(data);
  var obj = new this[sendType](data);
  // 代理模式，重置原方法，统一设置超时
  var start = obj.start;
  obj.start = function(){
    if (_.isObject(sd.para.is_debug) && sd.para.is_debug.storage && sd.store.requests) {
      sd.store.requests.push({
        name: this.server_url,
        initiatorType: this.img ? 'img' : 'xmlhttprequest', // todo: beacon
        entryType: "resource",
        requestData: this.data
      });
    }
    var me = this;
    start.apply(this, arguments);
    setTimeout(function(){
      me.isEnd(true);
    },sd.para.callback_timeout);
  };
  obj.end = function(){
    this.callback && this.callback();
    var self=this;
    setTimeout(function(){
      self.lastClear && self.lastClear();
    },sd.para.datasend_timeout-sd.para.callback_timeout);
  };
  obj.isEnd = function(isDelay){
    if(!this.received){
      this.received =  true;
      this.end();
      var self=this;
      if(isDelay){
        if(sd.para.queue_timeout-sd.para.callback_timeout <= 0){
          self.close();
        } else {
          setTimeout(function () {
            self.close();
          },sd.para.queue_timeout-sd.para.callback_timeout);
        }
      }else{
        self.close();
      }
    }
  };

  return obj;

};

dataSend.getSendType = function(data) {
  var supportedSendTypes = ['image', 'ajax', 'beacon'];
  var sendType = supportedSendTypes[0];

  if(data.config && _.indexOf(supportedSendTypes, data.config.send_type) > -1) {
    sendType = data.config.send_type;
  } else {
    sendType = sd.para.send_type;
  }

  if (sendType === 'beacon' && typeof navigator.sendBeacon !== "function") {
    sendType = 'image';
  }

  if (sendType === 'ajax' && _.isSupportCors() === false) {
    sendType = 'image';
  }

  return sendType;
};

dataSend.image = function(para){
  this.callback = para.callback;
  this.img = document.createElement('img');
  this.img.width = 1;
  this.img.height = 1;
  // 如果后端没有配置 access-allow-origin 会导致请求错误
  if (sd.para.img_use_crossorigin) {
    this.img.crossOrigin = 'anonymous';
  }
  this.data = para.data;
  this.server_url = dataSend.getSendUrl(para.server_url,para.data);
};
dataSend.image.prototype.start = function(){
  var me = this;
  if (sd.para.ignore_oom) {
    this.img.onload = function() {
      this.onload = null;
      this.onerror = null;
      this.onabort = null;
      me.isEnd();
    };
    this.img.onerror = function() {
      this.onload = null;
      this.onerror = null;
      this.onabort = null;
      me.isEnd();
    };
    this.img.onabort = function() {
      this.onload = null;
      this.onerror = null;
      this.onabort = null;
      me.isEnd();
    };
  }
  this.img.src = this.server_url;
};

dataSend.image.prototype.lastClear=function(){
  this.img.src="";
}

dataSend.ajax = function(para){
  this.callback = para.callback;
  this.server_url = para.server_url;
  this.data = dataSend.getSendData(para.data);
};
dataSend.ajax.prototype.start = function(){
  var me = this;
  _.ajax({
    url: this.server_url,
    type: 'POST',
    data: this.data,
    credentials: false,
    timeout:sd.para.datasend_timeout,
    cors:true,
    success:function(){
      me.isEnd();
    },
    error:function(){
      me.isEnd();
    }
  });
};

dataSend.beacon = function(para){
  this.callback = para.callback;
  this.server_url = para.server_url;
  this.data = dataSend.getSendData(para.data);
};

dataSend.beacon.prototype.start = function(){
  var me = this;
  if(typeof navigator === 'object' && typeof navigator.sendBeacon === 'function'){
    navigator.sendBeacon(this.server_url,this.data);
  }
  setTimeout(function(){
    me.isEnd();
  },40);
};



// 数据发送流程控制

var sendState = {};
sd.sendState = sendState;
// 发送队列
sendState.queue = _.autoExeQueue();

sendState.requestData = null;

sendState.getSendCall = function(data, config, callback) {
  // 点击图渲染模式不发数据
  if(sd.is_heatmap_render_mode){
    return false;
  }

  if(sd.readyState.state < 3){
    sd.log('初始化没有完成');
    return false;
  }

  data._track_id = Number(String(Math.random()).slice(2, 5) + String(Math.random()).slice(2, 4) + String((new Date()).getTime()).slice(-4));
  if (sd.para.use_client_time) {
    data._flush_time = (new Date()).getTime();
  }

  var originData = data;

  data = JSON.stringify(data);
  //sd.log(originData);

  this.requestData = {
    data: originData,
    config: config,
    callback: callback
  };

  if(!sd.para.use_app_track_config.use_app_track && sd.para.batch_send && localStorage.length < 200){
    sd.log(originData);
    sd.batchSend.add(this.requestData.data);
    return false;
  }

  // 打通app传数据给app
  if(sd.para.use_app_track_config.use_app_track === true || sd.para.use_app_track_config.use_app_track === 'only'){
    //如果有新版，优先用新版
    if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.sensorsdataNativeTracker && _.isObject(window.SensorsData_iOS_JS_Bridge) && window.SensorsData_iOS_JS_Bridge.sensorsdata_app_server_url) {
      if(sd.para.use_app_track_config.H5verify){
          sd.debug.apph5({
            data: originData,
            step: '4.4',
            output:'all'
          });
          window.webkit.messageHandlers.sensorsdataNativeTracker.postMessage(JSON.stringify({callType:'app_h5_track', data: _.extend({server_url:sd.para.server_url}, originData)}));
      }else{
        sd.debug.apph5({
          data: originData,
          step: '4.1',
          output:'all'
        });
        this.prepareServerUrl();
      }
    }else if(_.isObject(window.SensorsData_APP_New_H5_Bridge) && window.SensorsData_APP_New_H5_Bridge.sensorsdata_get_server_url && window.SensorsData_APP_New_H5_Bridge.sensorsdata_track){
      if(sd.para.use_app_track_config.H5verify){
        sd.debug.apph5({
          data: originData,
          step: '4.3',
          output:'all'
        });
        SensorsData_APP_New_H5_Bridge.sensorsdata_track(JSON.stringify(_.extend({server_url:sd.para.server_url},originData)));
      }else{
        sd.debug.apph5({
          data: originData,
          step: '4.2',
          output:'all'
        });
        this.prepareServerUrl();
      }
    }else if((typeof SensorsData_APP_JS_Bridge === 'object') && (SensorsData_APP_JS_Bridge.sensorsdata_verify || SensorsData_APP_JS_Bridge.sensorsdata_track)){
      // 如果有有校验的版本，优先用校验版
      if(SensorsData_APP_JS_Bridge.sensorsdata_verify){
        // 如果校验通过则结束，不通过则降级改成h5继续发送
        if(!SensorsData_APP_JS_Bridge.sensorsdata_verify(JSON.stringify(_.extend({server_url:sd.para.server_url},originData)))){
          if (sd.para.use_app_track_config.use_app_track_is_send) {
            sd.debug.apph5({
              data: originData,
              step: '3.1',
              output:'all'
            });
            this.prepareServerUrl();
          }
        }else{
          (typeof callback === 'function') && callback();
        }
      }else{
        SensorsData_APP_JS_Bridge.sensorsdata_track(JSON.stringify(_.extend({server_url:sd.para.server_url},originData)));
        (typeof callback === 'function') && callback();
      }
    }else if((/sensors-verify/.test(navigator.userAgent) || /sa-sdk-ios/.test(navigator.userAgent)) && !window.MSStream){
      var iframe = null;
      if(/sensors-verify/.test(navigator.userAgent)){
        var match = navigator.userAgent.match(/sensors-verify\/([^\s]+)/);
        if(match && match[0] && (typeof match[1] === 'string') && (match[1].split('?').length === 2)){
          match = match[1].split('?');
          var hostname = null;
          var project = null;
          try{
            hostname = _.URL(sd.para.server_url).hostname;
            project = _.URL(sd.para.server_url).searchParams.get('project') || 'default';
          }catch(e){};
          if (hostname && hostname === match[0] && project && project === match[1]) {
            iframe = document.createElement('iframe');
            iframe.setAttribute('src', 'sensorsanalytics://trackEvent?event=' + encodeURIComponent(JSON.stringify(_.extend({server_url:sd.para.server_url},originData))));
            document.documentElement.appendChild(iframe);
            iframe.parentNode.removeChild(iframe);
            iframe = null;
            (typeof callback === 'function') && callback();
          } else {
            if (sd.para.use_app_track_config.use_app_track_is_send) {
              sd.debug.apph5({
                data: originData,
                step: '3.2',
                output:'all'
              });
              this.prepareServerUrl();
            }
          }
        }
      }else{
        iframe = document.createElement('iframe');
        iframe.setAttribute('src', 'sensorsanalytics://trackEvent?event=' + encodeURIComponent(JSON.stringify(_.extend({server_url:sd.para.server_url},originData))));
        document.documentElement.appendChild(iframe);
        iframe.parentNode.removeChild(iframe);
        iframe = null;
        (typeof callback === 'function') && callback();
      }
    }else{
      if(sd.para.use_app_track_config.use_app_track === true && sd.para.use_app_track_config.use_app_track_is_send === true){
        sd.debug.apph5({
          data: originData,
          step: '2',
          output:'all'
        });
        this.prepareServerUrl();
      }
    }
  }else if(sd.para.use_app_track_config.use_app_track === 'mui'){
    if(_.isObject(window.plus) && window.plus.SDAnalytics && window.plus.SDAnalytics.trackH5Event){
      window.plus.SDAnalytics.trackH5Event(data);
    }
  } else{
    sd.debug.apph5({
      data: originData,
      step: '1',
      output:'code'
    });
    this.prepareServerUrl();
  }
  sd.log(originData);
};

sendState.prepareServerUrl = function(){
  if (typeof this.requestData.config === 'object' && this.requestData.config.server_url) {
    this.sendCall(this.requestData.config.server_url,this.requestData.callback);
  }else if(_.isArray(sd.para.server_url)){
    for(var i =0; i<sd.para.server_url.length;i++){
      this.sendCall(sd.para.server_url[i]);
    }
  }else{
    this.sendCall(sd.para.server_url,this.requestData.callback);
  }
};

sendState.sendCall = function(server_url,callback){
  var data = {
    server_url: server_url,
    data: JSON.stringify(this.requestData.data),
    callback: callback,
    config: this.requestData.config
  };
  if(_.isObject(sd.para.jsapp) && !sd.para.jsapp.isOnline && typeof sd.para.jsapp.setData === 'function'){
    delete data.callback;
    data = JSON.stringify(data);
    sd.para.jsapp.setData(data);
  }else{
    this.pushSend(data);
  }
};

sendState.pushSend = function(data){
  var instance = dataSend.getInstance(data);
  var me = this;
  instance.close = function(){
    me.queue.close();
  };
  this.queue.enqueue(instance);
};


var saEvent = {};

saEvent.checkOption = {
  // event和property里的key要是一个合法的变量名，由大小写字母、数字、下划线和$组成，并且首字符不能是数字。
  regChecks: {
    regName: /^((?!^distinct_id$|^original_id$|^time$|^properties$|^id$|^first_id$|^second_id$|^users$|^events$|^event$|^user_id$|^date$|^datetime$)[a-zA-Z_$][a-zA-Z\d_$]{0,99})$/i
  },
  checkPropertiesKey: function(obj) {
    var me = this, flag = true;
    _.each(obj, function(content, key) {
      if (!me.regChecks.regName.test(key)) {
        flag = false;
      }
    });
    return flag;
  },
  check: function(a, b) {
    if (typeof this[a] === 'string') {
      return this[this[a]](b);
    } else {
      return this[a](b);
    }
  },
  str: function(s) {
    if (!_.isString(s)) {
      sd.log('请检查参数格式,必须是字符串');
      //return false;
      return true;
    } else {
      return true;
    }
  },
  properties: function(p) {
    _.strip_sa_properties(p);
    if (p) {
      if (_.isObject(p)) {
        if (this.checkPropertiesKey(p)) {
          return true;
        } else {
          sd.log('properties 里的自定义属性名需要是合法的变量名，不能以数字开头，且只包含：大小写字母、数字、下划线，自定义属性不能以 $ 开头');
          //return false;
          return true;
        }
      } else {
        sd.log('properties可以没有，但有的话必须是对象');
        return true;
        //return false;
      }
    } else {
      return true;
    }
  },
  propertiesMust: function(p) {
    _.strip_sa_properties(p);
    if (p === undefined || !_.isObject(p) || _.isEmptyObject(p)) {
      sd.log('properties必须是对象且有值');
      return true;
      //return false;
    } else {
      if (this.checkPropertiesKey(p)) {
        return true;
      } else {
        sd.log('properties 里的自定义属性名需要是合法的变量名，不能以数字开头，且只包含：大小写字母、数字、下划线，自定义属性不能以 $ 开头');
        return true;
        //return false;
      }
    }
  },
  // event要检查name
  event: function(s) {
    if (!_.isString(s) || !this['regChecks']['regName'].test(s)) {
      sd.log('请检查参数格式，eventName 必须是字符串，且需是合法的变量名，即不能以数字开头，且只包含：大小写字母、数字、下划线和 $,其中以 $ 开头的表明是系统的保留字段，自定义事件名请不要以 $ 开头');
      //return false;
      return true;
    } else {
      return true;
    }

  },
  test_id: 'str',
  group_id: 'str',
  distinct_id: function(id) {
    if (_.isString(id) && /^.{1,255}$/.test(id)) {
      return true;
    } else {
      sd.log('distinct_id必须是不能为空，且小于255位的字符串');
      return false;
    }
  }
};

saEvent.check = function(p) {
  var flag = true;
  for (var i in p) {
    if (!this.checkOption.check(i, p[i])) {
      return false;
    }
  }
  return flag;
};

saEvent.send = function(p, callback) {
  var data = {
    distinct_id: store.getDistinctId(),
    lib: {
      $lib: 'js',
      $lib_method: 'code',
      $lib_version: String(sd.lib_version)
    },
    properties: {}
  };

  if (_.isObject(p) && _.isObject(p.properties) && !_.isEmptyObject(p.properties) && p.properties.$lib_detail) {
    data.lib.$lib_detail = p.properties.$lib_detail;
    delete p.properties.$lib_detail;
  }
  //debugger;
  _.extend(data, sd.store.getUnionId(), p);

  // 合并properties里的属性
  if (_.isObject(p.properties) && !_.isEmptyObject(p.properties)) {
    _.extend(data.properties, p.properties);
  }
  /*
  // 合并lib里的属性
  if (_.isObject(callback)) {
    _.extend(data.lib, callback);
  }
  */

  // profile时不传公用属性
  if (!p.type || p.type.slice(0, 7) !== 'profile') {
    // 传入的属性 > 当前页面的属性 > session的属性 > cookie的属性 > 预定义属性

    data.properties = _.extend({}, _.info.properties(), store.getProps(), store.getSessionProps(), _.info.currentProps, data.properties);
    if(sd.para.preset_properties.latest_referrer && !_.isString(data.properties.$latest_referrer)){
      data.properties.$latest_referrer = '取值异常';
      // TODO
      // Do NOT send data here, it will cause too much recursion.
      //_.jssdkDebug(data.properties,store.getProps());
      //sd.debug.jssdkDebug(data.properties, store.getProps());
    }
    if(sd.para.preset_properties.latest_search_keyword && !_.isString(data.properties.$latest_search_keyword)){
      data.properties.$latest_search_keyword = '取值异常';
    }
    if(sd.para.preset_properties.latest_traffic_source_type && !_.isString(data.properties.$latest_traffic_source_type)){
      data.properties.$latest_traffic_source_type = '取值异常';
    }
    if(sd.para.preset_properties.latest_landing_page && !_.isString(data.properties.$latest_landing_page)){
      data.properties.$latest_landing_page = '取值异常';
    }
  }

  // 如果$time是传入的就用，否则使用服务端时间
  if (data.properties.$time && _.isDate(data.properties.$time)) {
    data.time = data.properties.$time * 1;
    delete data.properties.$time;
  } else {
    if (sd.para.use_client_time) {
      data.time = (new Date()) * 1;
    }
  }
  // Parse super properties that added by registerPage()
  _.parseSuperProperties(data.properties);

  _.filterReservedProperties(data.properties);
  _.searchObjDate(data);
  _.searchObjString(data);
  // 兼容灼洲app端做的$project和$token而加的代码
  _.searchZZAppStyle(data);

  //去掉data里的$option
  var data_config = _.searchConfigData(data.properties);

  //判断是否要给数据增加新用户属性
  saNewUser.checkIsAddSign(data);
  saNewUser.checkIsFirstTime(data);

  sd.addReferrerHost(data);
  sd.addPropsHook(data);

  if (sd.para.debug_mode === true) {
    sd.log(data);
    this.debugPath(JSON.stringify(data), callback);
  } else {
    sd.sendState.getSendCall(data,data_config,callback);
  }

};

  // 发送debug数据请求
saEvent.debugPath = function(data, callback) {
  var _data = data; //存数据
  var url = '';
  if (sd.para.debug_mode_url.indexOf('?') !== -1) {
    url = sd.para.debug_mode_url + '&data=' + encodeURIComponent(_.base64Encode(data));
  } else {
    url = sd.para.debug_mode_url + '?data=' + encodeURIComponent(_.base64Encode(data));
  }

  _.ajax({
       url: url,
       type: 'GET',
       cors: true,
       header: {'Dry-Run': String(sd.para.debug_mode_upload)},
       success:function(data){
       // debug 模式下 提示框
        _.isEmptyObject(data) === true ? alert('debug数据发送成功' + _data) : alert('debug失败 错误原因' + JSON.stringify(data));
       }
     });

};

/*
cookie的数据存储
初始化cookie里的distinct_id
*/

  var store = sd.store = {
    requests: [],
      _sessionState: {},
      _state: {
        distinct_id: '',
        first_id: '',
        props: {}
      },
      getProps: function() {
        return this._state.props || {};
      },
      getSessionProps: function() {
        return this._sessionState;
      },
      getDistinctId: function() {
        return this._state._distinct_id || this._state.distinct_id;
      },
      getUnionId: function() {
        var obj = {};
        var firstId = this._state._first_id || this._state.first_id, distinct_id = this._state._distinct_id || this._state.distinct_id;
        if (firstId && distinct_id) {
          obj.login_id = distinct_id;
          obj.anonymous_id = firstId;
        } else {
          obj.anonymous_id = distinct_id;
        }
        return obj;
      },
      getFirstId: function(){
        return this._state._first_id || this._state.first_id;
      },
      toState: function(ds) {
        var state = null;
        if (ds != null && _.isJSONString(ds)) {
          state = JSON.parse(ds);
          this._state = _.extend(state);
          if (state.distinct_id) {
            if(typeof(state.props) === 'object'){
              for(var key in state.props){
                if(typeof state.props[key] === 'string'){
                  state.props[key] = state.props[key].slice(0, sd.para.max_referrer_string_length);
                }
              }
              this.save();
            }

          } else {
            this.set('distinct_id', _.UUID());
            sd.debug.distinct_id('1', ds);
          }
        } else {
          this.set('distinct_id', _.UUID());
          sd.debug.distinct_id('2', ds);
        }
      },
      initSessionState: function() {
        var ds = _.cookie.get('sensorsdata2015session');
        var state = null;
        if (ds !== null && (typeof (state = JSON.parse(ds)) === 'object')) {
          this._sessionState = state || {};
        }
      },

      setOnce: function(a, b) {
        if (!(a in this._state)) {
          this.set(a, b);
        }
      },
      set: function(name, value) {
        this._state = this._state || {};
        this._state[name] = value;
        // 如果set('first_id') 或者 set('distinct_id')，删除对应的临时属性
        if (name === 'first_id') {
          delete this._state._first_id;
        } else if (name === 'distinct_id') {
          delete this._state._distinct_id;
        }
        this.save();
      },
      // 针对当前页面修改
      change: function(name, value) {
        // 为临时属性名增加前缀 _ (下划线)
        this._state['_' + name] = value;
      },
      setSessionProps: function(newp) {
        var props = this._sessionState;
        _.extend(props, newp);
        this.sessionSave(props);
      },
      setSessionPropsOnce: function(newp) {
        var props = this._sessionState;
        _.coverExtend(props, newp);
        this.sessionSave(props);
      },
      setProps: function(newp,isCover) {
        var props = {};
        if(!isCover){
          props =  _.extend((this._state.props || {}), newp);
        }else{
          props = newp;
        }
        for(var key in props){
          if(typeof props[key] === 'string'){
            props[key] = props[key].slice(0, sd.para.max_referrer_string_length);
          }
        }
        this.set('props', props);
      },
      setPropsOnce: function(newp) {
        var props = this._state.props || {};
        _.coverExtend(props, newp);
        this.set('props', props);
      },
    clearAllProps: function(arr) {
      this._sessionState = {};
      if(_.isArray(arr) && arr.length > 0){
        for(var i=0;i<arr.length;i++){
          if(_.isString(arr[i]) && arr[i].indexOf('latest_') === -1 && arr[i] in this._state.props){
            delete this._state.props[arr[i]];
          }
        }
      }else{
        for(var i in this._state.props){
          if(i.indexOf('latest_') !== 1){
            delete this._state.props[i];
          }
        }
      }
      this.sessionSave({});
      this.save();
    },
    sessionSave: function(props) {
      this._sessionState = props;
      _.cookie.set('sensorsdata2015session', JSON.stringify(this._sessionState), 0);
    },
    save: function() {
      // 深拷贝避免修改原对象
      var copyState = JSON.parse(JSON.stringify(this._state));
      // 删除临时属性避免写入cookie
      delete copyState._first_id;
      delete copyState._distinct_id;
      _.cookie.set(this.getCookieName(), JSON.stringify(copyState), 73000, sd.para.cross_subdomain);
    },
    getCookieName: function(){
      var sub = '';
      if(sd.para.cross_subdomain === false){
        try {
          sub = _.URL(location.href).hostname;
        } catch (e) {
          // do nothing.
        }
        if(typeof sub === 'string' && sub !== ''){
          sub = 'sa_jssdk_2015_' + sub.replace(/\./g, '_');
        }else{
          sub = 'sa_jssdk_2015_root';
        }
      }else{
        sub = 'sensorsdata2015jssdkcross';
      }
      return sub;
    },
    init: function() {

      this.initSessionState();
      var uuid = _.UUID();
      var cross = _.cookie.get(this.getCookieName());
      if (cross === null) {
        // null肯定是首次，非null，看是否有distinct_id
        sd.is_first_visitor = true;

        this.set('distinct_id', uuid);
      } else {

        if (!_.isJSONString(cross) || !(JSON.parse(cross)).distinct_id){
          sd.is_first_visitor = true;
        }

        this.toState(cross);
      }


      // 如果没有跨域的cookie，且没有当前域cookie，那当前域的cookie和跨域cookie一致
        saNewUser.setDeviceId(uuid);

        //判断新用户
        saNewUser.storeInitCheck();
        saNewUser.checkIsFirstLatest();

      }
    };


// 检查是否是新用户（第一次种cookie，且在8个小时内的）
var saNewUser = {
  checkIsAddSign: function(data) {
    if (data.type === 'track') {
      if (_.cookie.getNewUser()) {
        data.properties.$is_first_day = true;
      } else {
        data.properties.$is_first_day = false;
      }
    }
  },
  is_first_visit_time: false,
  checkIsFirstTime: function(data) {
    if (data.type === 'track' && data.event === '$pageview') {
      if (this.is_first_visit_time) {
        data.properties.$is_first_time = true;
        this.is_first_visit_time = false;
      } else {
        data.properties.$is_first_time = false;
      }
    }
  },
  setDeviceId: function(uuid){
    // deviceid必须跨子域
    var device_id = null;
    var ds = _.cookie.get('sensorsdata2015jssdkcross');
    var state = {};
    if (ds != null && _.isJSONString(ds)) {
      state = JSON.parse(ds);
      if(state.$device_id) {
        device_id = state.$device_id;
      }
    }

    device_id = device_id || uuid;

    if(sd.para.cross_subdomain === true){
      store.set('$device_id',device_id);
    }else{
      state.$device_id = device_id;
      _.cookie.set('sensorsdata2015jssdkcross',JSON.stringify(state),null,true);
    }

    if(sd.para.is_track_device_id){
      _.info.currentProps.$device_id = device_id;
    }

  },
  storeInitCheck: function() {
    // 如果是新用户，种cookie
    if (sd.is_first_visitor) {

      var date = new Date();
      var obj = {
        h: 23 - date.getHours(),
        m: 59 - date.getMinutes(),
        s: 59 - date.getSeconds()
      };
      _.cookie.set(_.cookie.getCookieName('new_user'), '1', obj.h * 3600 + obj.m * 60 + obj.s + 's');
      // 如果是is_first_visit_time，且第一次，那就发数据
      this.is_first_visit_time = true;
    } else {
      // 如果没有这个cookie，肯定不是首日
      if (!_.cookie.getNewUser()) {
        this.checkIsAddSign = function(data) {
          if (data.type === 'track') {
            data.properties.$is_first_day = false;
          }
        };
      }
      // 如果不是第一次打开的用户，肯定不是首次访问
      this.checkIsFirstTime = function(data) {
        if (data.type === 'track' && data.event === '$pageview') {
          data.properties.$is_first_time = false;
        }
      }
    }
  },
  //检查是否是latest
  checkIsFirstLatest: function() {
    var url_domain = _.info.pageProp.url_domain;

    //去除详叔的坑，utm_source相关
     var latest_utms = ['$utm_source','$utm_medium', '$utm_campaign', '$utm_content', '$utm_term'];
     var props = store.getProps();
     for(var i =0;i<latest_utms.length;i++){
       if(latest_utms[i] in props){
         delete props[latest_utms[i]];
       }
     }
     store.setProps(props,true);


    // 判断最近一次，如果前向地址跟自己域名一致，且cookie中取不到值，认为有异常
    // 最近一次站外前向地址，如果域名不一致，就register为latest

    var latestObj={};

    if (url_domain === "") {
      url_domain = "url解析失败";
    }

    // 遍历 preset_properties 配置参数
    _.each(sd.para.preset_properties,function(value,key){
      // 忽略不以 latest_ 开头的配置项
      if (key.indexOf('latest_') === -1) {
        return false;
      }
      // 得到 latest_ 后面的字符串
      // 例如 utm, traffic_source_type, search_keyword, referrer, referrer_host, landing_page
      key = key.slice(7);
      // 配置值为 true
      if(value){
        if (key !== 'utm' && url_domain === "url解析失败") {
          latestObj['$latest_'+key]='url的domain解析失败';
        }else if (_.isReferralTraffic(document.referrer)) {
          switch (key){
            case 'traffic_source_type':
              latestObj['$latest_traffic_source_type']=_.getSourceFromReferrer();
              break;
            case 'referrer':
              latestObj['$latest_referrer']=_.info.pageProp.referrer;
              break;
            case 'search_keyword':
              latestObj['$latest_search_keyword']=_.getKeywordFromReferrer();
              break;
            case 'landing_page':
              latestObj['$latest_landing_page']=location.href;
              break;
          }
        }
      }else{
        // 如果当前配置是 latest_utm，遍历store._state.props 删除非 $latest_utm 和 _latest_ 开头的属性
        if(key === 'utm' && sd.store._state.props){
          for(var key1 in sd.store._state.props){
            if(key1.indexOf('$latest_utm') === 0 || key1.indexOf('_latest_') === 0){
              delete sd.store._state.props[key1];
            }
          }
        // 如果是非 latest_utm 配置项，从 store._state.props 中删掉此属性
        }else if(sd.store._state.props && (('$latest_'+key) in sd.store._state.props)){
          delete sd.store._state.props['$latest_'+key];
        }
      }
    });

    sd.register(latestObj);

    // utm
    if(sd.para.preset_properties.latest_utm){
      var allUtms = _.info.campaignParamsStandard('$latest_','_latest_');
      var $utms = allUtms.$utms;
      var otherUtms = allUtms.otherUtms;
      if (!_.isEmptyObject($utms)) {
        sd.register($utms);
      }
      if (!_.isEmptyObject(otherUtms)) {
        sd.register(otherUtms);
      }
    }


  }

};


/*
点击图和触达图数据采集的功能
*/

  var heatmap = sd.heatmap = {
    // 设置错误提示
    setNotice: function(web_url){
      sd.is_heatmap_render_mode = true;

      if(!sd.para.heatmap){
        sd.errorMsg = '您SDK没有配置开启点击图，可能没有数据！';
      }
      if(web_url && web_url[0] && web_url[1]){
        if(web_url[1].slice(0,5) === 'http:' && location.protocol === 'https'){
          sd.errorMsg = '您的当前页面是https的地址，神策分析环境也必须是https！';
        }
      }
      if(!sd.para.heatmap_url){
        sd.para.heatmap_url = location.protocol + '//static.sensorsdata.cn/sdk/'+ sd.lib_version + '/heatmap.min.js';
      }

    },
    getDomIndex: function (el) {
      if (!el.parentNode) return -1;
      var i=0;
      var nodeName = el.tagName;
      var list = el.parentNode.children;
      for (var n = 0; n < list.length; n++) {
        if (list[n].tagName === nodeName) {
          if(el === list[n]){
            return i;
          }
          else {
            i++;
          }
        }
      }
      return -1;
    },
      selector:function (el){
        var i = el.parentNode && 9 == el.parentNode.nodeType ? -1 : this.getDomIndex(el);
        if(el.getAttribute && el.getAttribute('id') && (!sd.para.heatmap || (sd.para.heatmap && sd.para.heatmap.element_selector !== 'not_use_id'))){
          return '#' + el.getAttribute('id');
        }else{
            return el.tagName.toLowerCase() + (~i ? ':nth-of-type(' + (i + 1) + ')' : '');
        }
      },
      getDomSelector : function(el,arr) {
        if(!el || !el.parentNode || !el.parentNode.children){
          return false;
        }
        arr = arr && arr.join ? arr : [];
        var name = el.nodeName.toLowerCase();
        if (!el || name === 'body' || 1 != el.nodeType) {
          arr.unshift('body');
          return arr.join(' > ');
        }
        arr.unshift(this.selector(el));
        if (el.getAttribute && el.getAttribute('id') && (sd.para.heatmap && sd.para.heatmap.element_selector !== 'not_use_id')) return arr.join(' > ');
        return this.getDomSelector(el.parentNode, arr);
      },
      na : function() {
        var a = document.documentElement.scrollLeft || window.pageXOffset;
        return parseInt(isNaN(a) ? 0 : a, 10);
      },
      i : function() {
        var a = 0;
        try {
          a = o.documentElement && o.documentElement.scrollTop || m.pageYOffset,
          a = isNaN(a) ? 0 : a;
        } catch (b) {
          a = 0;
        }
        return parseInt(a, 10);
      },
      getBrowserWidth : function() {
        var a = window.innerWidth || document.body.clientWidth;
        return isNaN(a) ? 0 : parseInt(a, 10);
      },
      getBrowserHeight : function() {
        var a = window.innerHeight || document.body.clientHeight;
        return isNaN(a) ? 0 : parseInt(a, 10);
      },
      getScrollWidth : function() {
        var a = parseInt(document.body.scrollWidth, 10);
        return isNaN(a) ? 0 : a;
      },
      W : function(a) {
        var b = parseInt(+a.clientX + +this.na(), 10);
        var a = parseInt(+a.clientY + +this.i(), 10);
        return {
          x : isNaN(b) ? 0 : b,
          y : isNaN(a) ? 0 : a
        }
      },
      start : function(ev, target, tagName, customProps, callback) {
        var userCustomProps = _.isObject(customProps) ? customProps : {};
        var userCallback = _.isFunction(callback) ? callback : _.isFunction(customProps) ? customProps : undefined;
        if(sd.para.heatmap && sd.para.heatmap.collect_element && !sd.para.heatmap.collect_element(target)){
          return false;
        }

        var selector = this.getDomSelector(target);
        var prop = _.getEleInfo({target:target});

        prop.$element_selector = selector ? selector : '';
        if(sd.para.heatmap && sd.para.heatmap.custom_property) {
          var customP = sd.para.heatmap.custom_property(target);
          if(_.isObject(customP)){
            prop = _.extend(prop,customP);
          }
        }
        prop = _.extend(prop, userCustomProps);
        if(tagName === 'a' && sd.para.heatmap && sd.para.heatmap.isTrackLink === true){
          _.trackLink({event:ev,target:target},'$WebClick',prop);
        }else{
          sd.track('$WebClick', prop, userCallback);
        }

      },
    hasElement:function(e){
    var path = e._getPath();
    if(_.isArray(path) && (path.length > 0) ){
      for(var i = 0;i<path.length;i++){
        if(path[i] && path[i].tagName && (path[i].tagName.toLowerCase() === 'a')){
          return path[i];
        }
      }
    }
    return false;
  },

  initScrollmap: function(){
    if (!_.isObject(sd.para.heatmap) || sd.para.heatmap.scroll_notice_map !== 'default') {
      return false;
    }

    /*
    if (sd.para.scrollmap && _.isFunction(sd.para.scrollmap.collect_url) && !sd.para.scrollmap.collect_url()) {
      return false;
    }
    */
    var checkPage = function() {
      if (sd.para.scrollmap && _.isFunction(sd.para.scrollmap.collect_url) && !sd.para.scrollmap.collect_url()) {
        return false;
      }
      return true;
    };

    var interDelay = function(param){
      var interDelay = {};
      interDelay.timeout = param.timeout || 1000;
      interDelay.func = param.func;
      interDelay.hasInit = false;
      interDelay.inter = null;
      interDelay.main = function(para,isClose){
        this.func(para,isClose);
        this.inter = null;
      };
      interDelay.go = function(isNoDelay){
        var me = this;
        var para = {};
        if(!this.inter){
          para.$viewport_position = document.documentElement && document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop || 0;
          para.$viewport_position = Math.round(para.$viewport_position) || 0;
          para.$viewport_height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0;
          para.$viewport_width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 0;
          //para.$screen_orientation = _.getScreenOrientation();
          //para.$device_pixel_ratio = (isNaN(window.devicePixelRatio) ? 1 : window.devicePixelRatio);
          if(isNoDelay){
            interDelay.main(para,true);
          }else{

            this.inter = setTimeout(function(){
              interDelay.main(para);
            }, this.timeout);

          }
        }
      };
      return interDelay;
    };


    var delayTime =  interDelay({
      timeout: 1000,
      func: function(para,isClose){
        var offsetTop= document.documentElement && document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop || 0;
        var current_time = new Date();
        var delay_time = current_time - this.current_time;
        if((delay_time > sd.para.heatmap.scroll_delay_time && offsetTop - para.$viewport_position !==0) || isClose){
          para.$url = location.href;
          para.$title = document.title;
          para.$url_path = location.pathname;
          para.event_duration = Math.min(sd.para.heatmap.scroll_event_duration, parseInt(delay_time)/1000);
          sd.track('$WebStay',para);
        }
        this.current_time = current_time;
      }
    });

    delayTime.current_time = new Date();


    _.addEvent(window,'scroll', function () {
      if (!checkPage()) {
        return false;
      }
      delayTime.go();
    });

    _.addEvent(window,'unload',function(){
      if (!checkPage()) {
        return false;
      }
      delayTime.go('notime');
    });


  },
  initHeatmap : function() {
    var that = this;
    if (!_.isObject(sd.para.heatmap) || sd.para.heatmap.clickmap !== 'default') {
      return false;
    }

    // 验证url，function成功就行，非function认为都是全部
    if (_.isFunction(sd.para.heatmap.collect_url) && !sd.para.heatmap.collect_url()) {
      return false;
    }

    if (sd.para.heatmap.collect_elements === 'all') {
      sd.para.heatmap.collect_elements = 'all';
    } else {
      sd.para.heatmap.collect_elements = 'interact';
    }

    if (sd.para.heatmap.collect_elements === 'all') {
      _.addEvent(document, 'click', function(e) {
        var ev = e || window.event;
        if(!ev){
          return false;
        }
        var target = ev.target || ev.srcElement;
        if(typeof target !== 'object'){
          return false;
        }
        if(typeof target.tagName !== 'string'){
          return false;
        }
        var tagName = target.tagName.toLowerCase();
        if(tagName === 'body' || tagName === 'html'){
          return false;
        }
        if(!target || !target.parentNode || !target.parentNode.children){
          return false;
        }
        var parent_ele = target.parentNode.tagName.toLowerCase();
        if(parent_ele === 'a' || parent_ele === 'button'){
          that.start(ev, target.parentNode, parent_ele);
        }else{
          that.start(ev, target, tagName);
        }
      });

    } else {
      _.addEvent(document, 'click', function(e) {
        var ev = e || window.event;
        if(!ev){
          return false;
        }
        var target = ev.target || ev.srcElement;
        if(typeof target !== 'object'){
          return false;
        }
        if(typeof target.tagName !== 'string'){
          return false;
        }
        var tagName = target.tagName.toLowerCase();
        if(tagName.toLowerCase() === 'body' || tagName.toLowerCase() === 'html'){
          return false;
        }
        if(!target || !target.parentNode || !target.parentNode.children){
          return false;
        }

        var parent_ele = target.parentNode;

        if(tagName === 'a' || tagName === 'button' || tagName === 'input' || tagName === 'textarea' || _.hasAttribute(target, 'data-sensors-click')){
          that.start(ev, target, tagName);
        }else if(parent_ele.tagName.toLowerCase() === 'button' || parent_ele.tagName.toLowerCase() === 'a'){
          that.start(ev, parent_ele, target.parentNode.tagName.toLowerCase());
        }else if(tagName === 'area' && parent_ele.tagName.toLowerCase() === 'map' && _.ry(parent_ele).prev().tagName && _.ry(parent_ele).prev().tagName.toLowerCase() === 'img'){
          that.start(ev, _.ry(parent_ele).prev(), _.ry(parent_ele).prev().tagName.toLowerCase());
        }else{
          var hasA = that.hasElement(e);
          if(hasA){
            that.start(ev, hasA, hasA.tagName.toLowerCase());
          }
        }
      });
    }

  },
  initAppH5status : function(){
    function checkProjectAndHost(appUrl){
      function getHostNameAndProject(url){
        var obj = {
          hostname:'',
          project:''
        };
        try{
          obj.hostname = _.URL(url).hostname;
          obj.project = _.URL(url).searchParams.get('project') || 'default';
        }catch(e){console.log(e)};
        return obj;
      }
      var appObj = getHostNameAndProject(appUrl);
      var H5Obj = getHostNameAndProject(sd.para.server_url);
      if(appObj.hostname === H5Obj.hostname && appObj.project === H5Obj.project){
         return true;
      }else{
        if(sd.para.use_app_track_config.white_list[0]){
          for(var i=0;i<sd.para.use_app_track_config.white_list.length;i++){
            var urlobj = getHostNameAndProject(sd.para.use_app_track_config.white_list[i]);
            if(urlobj.hostname === appObj.hostname && urlobj.project === appObj.project){
              return true;
            }
          }
        }
      }
      
    }
    if(sd.para.use_app_track_config.use_app_track === true){
      if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.sensorsdataNativeTracker && _.isObject(window.SensorsData_iOS_JS_Bridge) && window.SensorsData_iOS_JS_Bridge.sensorsdata_app_server_url) {
          if(checkProjectAndHost(window.SensorsData_iOS_JS_Bridge.sensorsdata_app_server_url)){
            sd.para.use_app_track_config.H5verify = true;
          }
         
      }else if(_.isObject(window.SensorsData_APP_New_H5_Bridge) && window.SensorsData_APP_New_H5_Bridge.sensorsdata_get_server_url && window.SensorsData_APP_New_H5_Bridge.sensorsdata_track){
        var app_server_url = window.SensorsData_APP_New_H5_Bridge.sensorsdata_get_server_url();
        if(app_server_url){
          if(checkProjectAndHost(app_server_url)){
            sd.para.use_app_track_config.H5verify = true;
          }
        }
     
      }
    } 
  },
  prepare:function(todo){
    var match = location.search.match(/sa-request-id=([^&#]+)/);
    var type = location.search.match(/sa-request-type=([^&#]+)/);
    var web_url = location.search.match(/sa-request-url=([^&#]+)/);

    var me = this;
    
    function isReady(data,type,url){
      if(sd.para.heatmap_url){
        _.loadScript({
           success:function(){
               setTimeout(function(){
                  if(typeof sa_jssdk_heatmap_render !== 'undefined'){
                   sa_jssdk_heatmap_render(sd,data,type,url);
                    if (typeof console === 'object' && typeof console.log === 'function') {
                      if (!(sd.heatmap_version && (sd.heatmap_version === sd.lib_version))) {
                        console.log('heatmap.js与sensorsdata.js版本号不一致，可能存在风险!');
                      }
                    }
                  }
               },0);
           },
           error:function(){},
           type:'js',
           url: sd.para.heatmap_url
       });
     }else{
       sd.log('没有指定heatmap_url的路径');
     }

    }
    // 如果有id，才有可能是首次，首次的时候把web_url存进去
    if(match && match[0] && match[1]){
      heatmap.setNotice(web_url);
      if(_.sessionStorage.isSupport()){
        if(web_url && web_url[0] && web_url[1]){
          sessionStorage.setItem('sensors_heatmap_url',decodeURIComponent(web_url[1]));
        }
        sessionStorage.setItem('sensors_heatmap_id',match[1]);

          if(type && type[0] && type[1]){
            if(type[1] === '1' || type[1] === '2' || type[1] === '3'){
                type = type[1];
                sessionStorage.setItem('sensors_heatmap_type',type);
            }else{
                type = null;
            }
          }else{
              if(sessionStorage.getItem('sensors_heatmap_type') !== null){
                type = sessionStorage.getItem('sensors_heatmap_type');
              }else{
                type = null;
              }
          }
      }
      isReady(match[1],type);
    } else if(_.sessionStorage.isSupport() && typeof sessionStorage.getItem('sensors_heatmap_id') === 'string'){
      heatmap.setNotice();
      isReady(sessionStorage.getItem('sensors_heatmap_id'),sessionStorage.getItem('sensors_heatmap_type'),location.href);
    }else{
      me.initAppH5status();   
      todo();
      //进入热力图采集模式
      if (_.isObject(sd.para.heatmap)) {
        this.initHeatmap();
        this.initScrollmap();
      }
    }



    }
  };




sd.init = function(para){
  // 标志已经init过
  if(sd.readyState && sd.readyState.state && sd.readyState.state >= 2){
    return false;
  }
  sd.setInitVar();
  sd.readyState.setState(2);
  sd.initPara(para);

  function app_js_bridge(){
    var app_info = null;
    var todo = null;
    function setAppInfo(data){
      app_info = data;
      if(_.isJSONString(app_info)){
        app_info = JSON.parse(app_info);
      }
      if(todo){
        todo(app_info);
        todo = null;
        app_info = null;
      }
    }
    //android
    function getAndroid(){
      if(typeof window.SensorsData_APP_JS_Bridge === 'object' && window.SensorsData_APP_JS_Bridge.sensorsdata_call_app){
        app_info = SensorsData_APP_JS_Bridge.sensorsdata_call_app();
        if(_.isJSONString(app_info)){
          app_info = JSON.parse(app_info);
        }
      }
    }
    //ios
    window.sensorsdata_app_js_bridge_call_js = function(data){
      setAppInfo(data);
    };
    // 通知iOS
    function calliOS() {
      if (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) {
        var iframe = document.createElement("iframe");
        iframe.setAttribute("src", "sensorsanalytics://getAppInfo");
        document.documentElement.appendChild(iframe);
        iframe.parentNode.removeChild(iframe);
        iframe = null;
      }
    }
    sd.getAppStatus = function(func){
      calliOS();
      //先获取能直接取到的安卓，ios是异步的不需要操作
      getAndroid();
      // 不传参数，直接返回数据
      if(!func){
        return app_info;
        app_info = null;
      }else{
        //如果传参数，保存参数。如果有数据直接执行，没数据时保存
        if(app_info === null){
          todo = func;
        }else{
          func(app_info);
          app_info = null;
        }
      }
    };
  }

  heatmap.prepare(function(){
    app_js_bridge();
    // 初始化referrer等页面属性 1.6
    _.info.initPage();

    if(sd.para.is_track_single_page){
      _.addSinglePageEvent(function (last_url) {
        var sendData = function(extraData) {
          extraData = extraData || {};
          if (last_url !== location.href) {
            _.info.pageProp.referrer = last_url;
            sd.quick("autoTrack", _.extend({$url: location.href, $referrer: last_url}, extraData));
          }
        };
        if (typeof sd.para.is_track_single_page === "boolean") {
          sendData();
        } else if (typeof sd.para.is_track_single_page === "function") {
          var returnValue = sd.para.is_track_single_page();
          if (_.isObject(returnValue)) {
            sendData(returnValue);
          } else if (returnValue === true) {
            sendData();
          }
        }
      });
    }
    // 支持localstorage且开启了batch_send
    if (sd.para.batch_send) {
      sd.batchSend.batchInterval();
    }
    // 初始化distinct_id
    sd.store.init();

    sd.readyState.setState(3);
    // 发送数据
    if(sd._q && _.isArray(sd._q) && sd._q.length > 0 ){
      _.each(sd._q, function(content) {
        sd[content[0]].apply(sd, Array.prototype.slice.call(content[1]));
      });
    }

  });

};

var methods = ['track','quick','register','registerPage','registerOnce','trackSignup', 'setProfile','setOnceProfile','appendProfile', 'incrementProfile', 'deleteProfile', 'unsetProfile', 'identify','login','logout','trackLink','clearAllRegister'];

_.each(methods, function(method) {
  var oldFunc = sd[method];
  sd[method] = function() {
    if (!sd.readyState.getState()) {
      try {
        console.error('请先初始化神策JS SDK');
      } catch (e) {}
      return;
    }
    return oldFunc.apply(sd, arguments);
  };
});



if (typeof window['sensorsDataAnalytic201505'] === 'string'){
  //异步或者同步
  sd.setPreConfig(window[sensorsDataAnalytic201505]);
  window[sensorsDataAnalytic201505] = sd;
  sd.init();
  window['sensorsDataAnalytic201505'] = sd;
} else if (typeof window['sensorsDataAnalytic201505'] === 'undefined'){
  //module模式，或者webpack打包
  window['sensorsDataAnalytic201505'] = sd;
  return sd;
} else {
  //已经使用过以上两种方式的话
  return window['sensorsDataAnalytic201505'];
}




}catch(err){
  if (typeof console === 'object' && console.log) {
    try {console.log(err)} catch (e) {};
  }
}



});